| // Copyright 2017 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/fileapi/recent_model.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| |
| #include "base/files/file_path.h" |
| #include "base/functional/bind.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "chrome/browser/ash/fileapi/recent_file.h" |
| #include "chrome/browser/ash/fileapi/recent_model_factory.h" |
| #include "chrome/browser/ash/fileapi/test/fake_recent_source.h" |
| #include "chrome/browser/ash/fileapi/test/recent_file_matcher.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "storage/browser/file_system/file_system_url.h" |
| #include "storage/common/file_system/file_system_types.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| namespace fmp = extensions::api::file_manager_private; |
| |
| base::Time ModifiedTime(int64_t seconds_since_unix_epoch) { |
| return base::Time::FromSecondsSinceUnixEpoch(seconds_since_unix_epoch); |
| } |
| |
| base::FilePath FilePath(const char* name) { |
| return base::FilePath(name); |
| } |
| |
| RecentFile MakeRecentFile(const base::FilePath& path, |
| const base::Time& last_modified) { |
| storage::FileSystemURL url = storage::FileSystemURL::CreateForTest( |
| blink::StorageKey(), storage::kFileSystemTypeLocal, path); |
| return RecentFile(url, last_modified); |
| } |
| |
| std::unique_ptr<RecentSource> MakeRecentSource( |
| uint32_t lag_ms, |
| const std::vector<RecentFile> files) { |
| auto source = std::make_unique<FakeRecentSource>(); |
| if (lag_ms == 0) { |
| source->AddProducer(std::make_unique<FileProducer>( |
| base::Milliseconds(0), |
| std::initializer_list<RecentFile>{files[0], files[1]})); |
| } else { |
| source->AddProducer(std::make_unique<FileProducer>( |
| base::Milliseconds(lag_ms), |
| std::initializer_list<RecentFile>{files[0]})); |
| source->AddProducer(std::make_unique<FileProducer>( |
| base::Milliseconds(2 * lag_ms), |
| std::initializer_list<RecentFile>{files[1]})); |
| } |
| return source; |
| } |
| |
| // A helper method that generates two recent sources. If lag is set to 0 all |
| // files of the recent source are delivered at once at 0ms delay. Otherwise, |
| // one file is delivered with the given lag, and the other with twice the lag. |
| // If, say, one was to call this method with lags 100ms and 150ms, this would |
| // allow one to stagger file delivery as follows: |
| // |
| // 0ms 100ms 150ms 200ms 300ms |
| // s1: aaa.jpg ccc.mp4 |
| // s2: bbb.png ddd.ogg |
| std::vector<std::unique_ptr<RecentSource>> BuildDefaultSourcesWithLag( |
| uint32_t lag1_ms, |
| uint32_t lag2_ms) { |
| RecentFile files[] = { |
| // Source 1 files: |
| MakeRecentFile(FilePath("aaa.jpg"), ModifiedTime(1)), |
| MakeRecentFile(FilePath("ccc.mp4"), ModifiedTime(3)), |
| // Source 2 files: |
| MakeRecentFile(FilePath("bbb.png"), ModifiedTime(2)), |
| MakeRecentFile(FilePath("ddd.ogg"), ModifiedTime(4)), |
| }; |
| std::vector<std::unique_ptr<RecentSource>> sources; |
| sources.emplace_back(MakeRecentSource(lag1_ms, {files[0], files[1]})); |
| sources.emplace_back(MakeRecentSource(lag2_ms, {files[2], files[3]})); |
| return sources; |
| } |
| |
| std::vector<std::unique_ptr<RecentSource>> BuildDefaultSources() { |
| return BuildDefaultSourcesWithLag(0, 0); |
| } |
| |
| std::vector<RecentFile> GetRecentFiles(RecentModel* model, |
| const RecentModelOptions& options) { |
| std::vector<RecentFile> files; |
| |
| base::RunLoop run_loop; |
| |
| model->GetRecentFiles( |
| /*file_system_context=*/nullptr, /*origin=*/GURL(), |
| /*query=*/"", options, |
| base::BindOnce( |
| [](base::RunLoop* run_loop, std::vector<RecentFile>* files_out, |
| const std::vector<RecentFile>& files) { |
| *files_out = files; |
| run_loop->Quit(); |
| }, |
| &run_loop, &files)); |
| |
| run_loop.Run(); |
| |
| return files; |
| } |
| |
| } // namespace |
| |
| class RecentModelTest : public testing::Test { |
| public: |
| RecentModelTest() |
| : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| |
| void SetUp() override { |
| source_specs_.emplace_back( |
| RecentSourceSpec{.volume_type = fmp::VolumeType::kTesting}); |
| } |
| |
| void TearDown() override { source_specs_.clear(); } |
| |
| protected: |
| using RecentSourceList = std::vector<std::unique_ptr<RecentSource>>; |
| using RecentSourceListFactory = base::RepeatingCallback<RecentSourceList()>; |
| |
| RecentModel* CreateRecentModel(RecentSourceListFactory source_list_factory) { |
| return static_cast<RecentModel*>( |
| RecentModelFactory::GetInstance()->SetTestingFactoryAndUse( |
| &profile_, |
| base::BindRepeating( |
| [](const RecentSourceListFactory& source_list_factory, |
| content::BrowserContext* context) |
| -> std::unique_ptr<KeyedService> { |
| return RecentModel::CreateForTest(source_list_factory.Run()); |
| }, |
| std::move(source_list_factory)))); |
| } |
| |
| std::vector<RecentFile> BuildModelAndGetRecentFiles( |
| RecentSourceListFactory source_list_factory, |
| size_t max_files, |
| const base::TimeDelta& cutoff_delta, |
| RecentModel::FileType file_type, |
| bool invalidate_cache) { |
| RecentModel* model = CreateRecentModel(source_list_factory); |
| RecentModelOptions options; |
| options.scan_timeout = base::Milliseconds(500); |
| options.max_files = max_files; |
| options.file_type = file_type; |
| options.invalidate_cache = invalidate_cache; |
| options.now_delta = cutoff_delta; |
| options.source_specs = source_specs_; |
| |
| return GetRecentFiles(model, options); |
| } |
| |
| std::vector<RecentSourceSpec> source_specs_; |
| content::BrowserTaskEnvironment task_environment_; |
| TestingProfile profile_; |
| }; |
| |
| TEST_F(RecentModelTest, GetRecentFiles) { |
| std::vector<RecentFile> files = BuildModelAndGetRecentFiles( |
| base::BindRepeating(&BuildDefaultSources), 10, base::Days(30), |
| RecentModel::FileType::kAll, false); |
| |
| ASSERT_EQ(4u, files.size()); |
| EXPECT_THAT(files[0], IsRecentFile(FilePath("ddd.ogg"), ModifiedTime(4))); |
| EXPECT_THAT(files[1], IsRecentFile(FilePath("ccc.mp4"), ModifiedTime(3))); |
| EXPECT_THAT(files[2], IsRecentFile(FilePath("bbb.png"), ModifiedTime(2))); |
| EXPECT_THAT(files[3], IsRecentFile(FilePath("aaa.jpg"), ModifiedTime(1))); |
| } |
| |
| TEST_F(RecentModelTest, GetRecentFiles_MaxFiles) { |
| std::vector<RecentFile> files = BuildModelAndGetRecentFiles( |
| base::BindRepeating(&BuildDefaultSources), 3, base::Days(30), |
| RecentModel::FileType::kAll, false); |
| |
| ASSERT_EQ(3u, files.size()); |
| EXPECT_THAT(files[0], IsRecentFile(FilePath("ddd.ogg"), ModifiedTime(4))); |
| EXPECT_THAT(files[1], IsRecentFile(FilePath("ccc.mp4"), ModifiedTime(3))); |
| EXPECT_THAT(files[2], IsRecentFile(FilePath("bbb.png"), ModifiedTime(2))); |
| } |
| |
| TEST_F(RecentModelTest, GetRecentFiles_CutoffTime) { |
| // TODO(b:307455066): Fix last modified time in created files. |
| // Files created for the tests have last modified time set to 1, 2, 3, and 4 |
| // seconds since Unix epoch. This allows for last modified time checks to be |
| // performed independently of when the test is run. However, this is not |
| // ideal. First, there is a repetitive base::Time::FromSecondsSinceUnixEpoch |
| // scattered in the tests. Changing, say, the last modified time for aaa.jpg |
| // would require changing it everywhere in the tests. In addition, now that |
| // one can pass time delta to GetRecentFiles method, this makes testing |
| // fragile as we need to hit the right span of a few seconds between now and |
| // the start of the Unix epoch. |
| std::vector<RecentFile> files = BuildModelAndGetRecentFiles( |
| base::BindRepeating(&BuildDefaultSources), 10, |
| base::Time::Now() - base::Time::FromMillisecondsSinceUnixEpoch(2500), |
| RecentModel::FileType::kAll, false); |
| |
| ASSERT_EQ(2u, files.size()); |
| EXPECT_THAT(files[0], IsRecentFile(FilePath("ddd.ogg"), ModifiedTime(4))); |
| EXPECT_THAT(files[1], IsRecentFile(FilePath("ccc.mp4"), ModifiedTime(3))); |
| } |
| |
| TEST_F(RecentModelTest, GetRecentFiles_UmaStats) { |
| base::HistogramTester histogram_tester; |
| |
| BuildModelAndGetRecentFiles( |
| base::BindRepeating([]() { return RecentSourceList(); }), 10, |
| base::Days(30), RecentModel::FileType::kAll, false); |
| |
| histogram_tester.ExpectTotalCount(RecentModel::kLoadHistogramName, 1); |
| } |
| |
| TEST_F(RecentModelTest, GetRecentFiles_Audio) { |
| std::vector<RecentFile> files = BuildModelAndGetRecentFiles( |
| base::BindRepeating(&BuildDefaultSources), 10, base::Days(30), |
| RecentModel::FileType::kAudio, false); |
| |
| ASSERT_EQ(1u, files.size()); |
| EXPECT_THAT(files[0], IsRecentFile(FilePath("ddd.ogg"), ModifiedTime(4))); |
| } |
| |
| TEST_F(RecentModelTest, GetRecentFiles_Image) { |
| std::vector<RecentFile> files = BuildModelAndGetRecentFiles( |
| base::BindRepeating(&BuildDefaultSources), 10, base::Days(30), |
| RecentModel::FileType::kImage, false); |
| |
| ASSERT_EQ(2u, files.size()); |
| EXPECT_THAT(files[0], IsRecentFile(FilePath("bbb.png"), ModifiedTime(2))); |
| EXPECT_THAT(files[1], IsRecentFile(FilePath("aaa.jpg"), ModifiedTime(1))); |
| } |
| |
| TEST_F(RecentModelTest, GetRecentFiles_Video) { |
| std::vector<RecentFile> files = BuildModelAndGetRecentFiles( |
| base::BindRepeating(&BuildDefaultSources), 10, base::Days(30), |
| RecentModel::FileType::kVideo, false); |
| |
| ASSERT_EQ(1u, files.size()); |
| EXPECT_THAT(files[0], IsRecentFile(FilePath("ccc.mp4"), ModifiedTime(3))); |
| } |
| |
| TEST_F(RecentModelTest, GetRecentFiles_OneSourceIsLate) { |
| std::vector<RecentFile> files = BuildModelAndGetRecentFiles( |
| base::BindRepeating(&BuildDefaultSourcesWithLag, 100, 501), 10, |
| base::Days(30), RecentModel::FileType::kAll, false); |
| |
| ASSERT_EQ(2u, files.size()); |
| EXPECT_THAT(files[0], IsRecentFile(FilePath("ccc.mp4"), ModifiedTime(3))); |
| EXPECT_THAT(files[1], IsRecentFile(FilePath("aaa.jpg"), ModifiedTime(1))); |
| } |
| |
| TEST_F(RecentModelTest, GetRecentFiles_FirstSourceIsPartiallyLate) { |
| std::vector<RecentFile> files = BuildModelAndGetRecentFiles( |
| base::BindRepeating(&BuildDefaultSourcesWithLag, 499, 111), 10, |
| base::Days(30), RecentModel::FileType::kAll, false); |
| |
| ASSERT_EQ(3u, files.size()); |
| EXPECT_THAT(files[0], IsRecentFile(FilePath("ddd.ogg"), ModifiedTime(4))); |
| EXPECT_THAT(files[1], IsRecentFile(FilePath("bbb.png"), ModifiedTime(2))); |
| EXPECT_THAT(files[2], IsRecentFile(FilePath("aaa.jpg"), ModifiedTime(1))); |
| } |
| |
| TEST_F(RecentModelTest, GetRecentFiles_SecondSourceIsPartiallyLate) { |
| std::vector<RecentFile> files = BuildModelAndGetRecentFiles( |
| base::BindRepeating(&BuildDefaultSourcesWithLag, 50, 251), 10, |
| base::Days(30), RecentModel::FileType::kAll, false); |
| |
| ASSERT_EQ(3u, files.size()); |
| EXPECT_THAT(files[0], IsRecentFile(FilePath("ccc.mp4"), ModifiedTime(3))); |
| EXPECT_THAT(files[1], IsRecentFile(FilePath("bbb.png"), ModifiedTime(2))); |
| EXPECT_THAT(files[2], IsRecentFile(FilePath("aaa.jpg"), ModifiedTime(1))); |
| } |
| |
| TEST_F(RecentModelTest, GetRecentFiles_NoSourceIsLate) { |
| std::vector<RecentFile> files = BuildModelAndGetRecentFiles( |
| base::BindRepeating(&BuildDefaultSourcesWithLag, 249, 111), 10, |
| base::Days(30), RecentModel::FileType::kAll, false); |
| |
| ASSERT_EQ(4u, files.size()); |
| EXPECT_THAT(files[0], IsRecentFile(FilePath("ddd.ogg"), ModifiedTime(4))); |
| EXPECT_THAT(files[1], IsRecentFile(FilePath("ccc.mp4"), ModifiedTime(3))); |
| EXPECT_THAT(files[2], IsRecentFile(FilePath("bbb.png"), ModifiedTime(2))); |
| EXPECT_THAT(files[3], IsRecentFile(FilePath("aaa.jpg"), ModifiedTime(1))); |
| } |
| |
| TEST_F(RecentModelTest, GetRecentFiles_AllSourcesAreLate) { |
| std::vector<RecentFile> files = BuildModelAndGetRecentFiles( |
| base::BindRepeating(&BuildDefaultSourcesWithLag, 501, 502), 10, |
| base::Days(30), RecentModel::FileType::kAll, false); |
| |
| ASSERT_EQ(0u, files.size()); |
| } |
| |
| // Checks the behavior of the recent model if we have two requests issued with |
| // different queries at the almost same time. |
| TEST_F(RecentModelTest, MultipleRequests) { |
| // Creates laggy sources, so that we can call GetRecentFiles twice. |
| RecentModel* model = CreateRecentModel( |
| base::BindRepeating(&BuildDefaultSourcesWithLag, 500, 500)); |
| |
| std::vector<RecentFile> files_1; |
| std::vector<RecentFile> files_2; |
| base::RunLoop loop; |
| int calls_completed_count = 0; |
| |
| // First request; fills files_1. We use query "aaa" |
| RecentModelOptions options; |
| options.max_files = 10; |
| options.source_specs = source_specs_; |
| model->GetRecentFiles( |
| /*file_system_context=*/nullptr, /*origin=*/GURL(), |
| /*query=*/"aaa", options, |
| base::BindLambdaForTesting([&](const std::vector<RecentFile>& files) { |
| files_1 = files; |
| if (++calls_completed_count == 2) { |
| loop.Quit(); |
| } |
| })); |
| |
| // Use a different query, expect different results. |
| model->GetRecentFiles( |
| /*file_system_context=*/nullptr, /*origin=*/GURL(), |
| /*query=*/"bbb", options, |
| base::BindLambdaForTesting([&](const std::vector<RecentFile>& files) { |
| files_2 = files; |
| if (++calls_completed_count == 2) { |
| loop.Quit(); |
| } |
| })); |
| |
| loop.Run(); |
| |
| ASSERT_EQ(1u, files_1.size()); |
| EXPECT_THAT(files_1[0], IsRecentFile(FilePath("aaa.jpg"), ModifiedTime(1))); |
| |
| ASSERT_EQ(1u, files_2.size()); |
| EXPECT_THAT(files_2[0], IsRecentFile(FilePath("bbb.png"), ModifiedTime(2))); |
| } |
| |
| // Do not use RecentModelTest fixture, because we need to get a reference of |
| // RecentModel and call GetRecentFiles() multiple times. |
| TEST(RecentModelCacheTest, GetRecentFiles_InvalidateCache) { |
| content::BrowserTaskEnvironment task_environment; |
| std::unique_ptr<RecentModel> model = |
| RecentModel::CreateForTest(BuildDefaultSources()); |
| |
| RecentModelOptions options; |
| options.max_files = 10; |
| options.now_delta = base::TimeDelta::Max(); |
| options.source_specs.emplace_back( |
| RecentSourceSpec{.volume_type = fmp::VolumeType::kTesting}); |
| std::vector<RecentFile> files1 = GetRecentFiles(model.get(), options); |
| ASSERT_EQ(4u, files1.size()); |
| |
| // Shutdown() will clear all sources. |
| model->Shutdown(); |
| |
| // The returned file list should still has 4 files even though all sources has |
| // been cleared in Shutdown(), because it hits cache. |
| std::vector<RecentFile> files2 = GetRecentFiles(model.get(), options); |
| ASSERT_EQ(4u, files2.size()); |
| |
| // Inalidate cache and query again. |
| options.invalidate_cache = true; |
| std::vector<RecentFile> files3 = GetRecentFiles(model.get(), options); |
| ASSERT_EQ(0u, files3.size()); |
| } |
| |
| TEST(RecentModelSourceRestrictions, QueryNonexistingSources) { |
| content::BrowserTaskEnvironment task_environment; |
| std::unique_ptr<RecentModel> model = |
| RecentModel::CreateForTest(BuildDefaultSources()); |
| |
| RecentModelOptions options; |
| options.max_files = 10; |
| options.now_delta = base::TimeDelta::Max(); |
| options.source_specs.emplace_back( |
| RecentSourceSpec{.volume_type = fmp::VolumeType::kDrive}); |
| options.source_specs.emplace_back( |
| RecentSourceSpec{.volume_type = fmp::VolumeType::kDownloads}); |
| std::vector<RecentFile> files = GetRecentFiles(model.get(), options); |
| // Test sources have kTesting as the volume type; thus fetching files from |
| // other volumes should result in empty recent files vector. |
| EXPECT_TRUE(files.empty()); |
| // Manual shutdown to clear sources_ vector in the model. |
| model->Shutdown(); |
| } |
| |
| } // namespace ash |