// Copyright (c) 2012 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 "chrome/browser/chromeos/drive/drive_prefetcher.h" #include #include #include "base/bind.h" #include "base/message_loop.h" #include "base/run_loop.h" #include "chrome/browser/chromeos/drive/drive.pb.h" #include "chrome/browser/chromeos/drive/drive_test_util.h" #include "chrome/browser/chromeos/drive/mock_drive_file_system.h" #include "content/public/test/test_browser_thread.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::StrictMock; using ::testing::_; namespace drive { namespace { // Enumeration values to represent the type of drive entries. enum TestEntryType { TYPE_DIRECTORY, TYPE_REGULAR_FILE, TYPE_HOSTED_FILE, }; // TestEntry represents a dummy entry for mocking a filesystem. struct TestEntry { const FilePath::CharType* path; TestEntryType entry_type; int64 last_access; const char* resource_id; // Checks whether this TestEntry is the direct content of the |directory|. bool IsDirectChildOf(const FilePath& directory) const { return FilePath(path).DirName() == directory; } // Converts this TestEntry to DriveEntryProto, which is the real data // structure used in DriveFileSystem. DriveEntryProto ToDriveEntryProto() const { DriveEntryProto entry; entry.set_base_name(FilePath(path).BaseName().value()); entry.mutable_file_info()->set_is_directory(entry_type == TYPE_DIRECTORY); if (entry_type != TYPE_DIRECTORY) { entry.mutable_file_specific_info()->set_is_hosted_document( entry_type == TYPE_HOSTED_FILE); } entry.mutable_file_info()->set_last_accessed(last_access); entry.set_resource_id(resource_id); return entry; } }; // Mocks DriveFileSystem::GetFileByResourceId. It records the requested // resource_id (arg0) to |fetched_list|, and calls back a successful completion. ACTION_P(MockGetFile, fetched_list) { fetched_list->push_back(arg0); arg1.Run(DRIVE_FILE_OK, FilePath(), std::string(), REGULAR_FILE); } // Mocks DriveFileSystem::ReadDirectory. It holds the flat list of all entries // in the mock filesystem in |test_entries|, and when it is called to read a // |directory|, it selects only the direct children of the directory. ACTION_P(MockReadDirectory, test_entries) { const FilePath& directory = arg0; const ReadDirectoryWithSettingCallback& callback = arg1; scoped_ptr entries(new DriveEntryProtoVector); for (size_t i = 0; i < test_entries.size(); ++i) { if (test_entries[i].IsDirectChildOf(directory)) entries->push_back(test_entries[i].ToDriveEntryProto()); } callback.Run(DRIVE_FILE_OK, false /* hide_hosted_document */, entries.Pass()); } const TestEntry kEmptyDrive[] = { { FILE_PATH_LITERAL("drive"), TYPE_DIRECTORY, 0, "id:drive" }, }; const TestEntry kOneFileDrive[] = { { FILE_PATH_LITERAL("drive"), TYPE_DIRECTORY, 0, "id:drive" }, { FILE_PATH_LITERAL("drive/abc.txt"), TYPE_REGULAR_FILE, 1, "id:abc" }, }; const char* kExpectedOneFile[] = { "id:abc" }; const TestEntry kComplexDrive[] = { { FILE_PATH_LITERAL("drive"), TYPE_DIRECTORY, 0, "id:root" }, { FILE_PATH_LITERAL("drive/a"), TYPE_DIRECTORY, 0, "id:a" }, { FILE_PATH_LITERAL("drive/a/foo.txt"), TYPE_REGULAR_FILE, 3, "id:foo1" }, { FILE_PATH_LITERAL("drive/a/b"), TYPE_DIRECTORY, 8, "id:b" }, { FILE_PATH_LITERAL("drive/a/b/bar.jpg"), TYPE_REGULAR_FILE, 5, "id:bar1" }, { FILE_PATH_LITERAL("drive/a/b/new.gdoc"), TYPE_HOSTED_FILE, 7, "id:new" }, { FILE_PATH_LITERAL("drive/a/buz.zip"), TYPE_REGULAR_FILE, 4, "id:buz1" }, { FILE_PATH_LITERAL("drive/a/old.gdoc"), TYPE_HOSTED_FILE, 1, "id:old" }, { FILE_PATH_LITERAL("drive/c"), TYPE_DIRECTORY, 0, "id:c" }, { FILE_PATH_LITERAL("drive/c/foo.txt"), TYPE_REGULAR_FILE, 2, "id:foo2" }, { FILE_PATH_LITERAL("drive/c/buz.zip"), TYPE_REGULAR_FILE, 1, "id:buz2" }, { FILE_PATH_LITERAL("drive/bar.jpg"), TYPE_REGULAR_FILE, 6, "id:bar2" }, }; const char* kTop3Files[] = { "id:bar2", "id:bar1", "id:buz1" }; const char* kAllRegularFiles[] = { "id:bar2", "id:bar1", "id:buz1", "id:foo1", "id:foo2", "id:buz2", }; } // namespace class DrivePrefetcherTest : public testing::Test { public: DrivePrefetcherTest() : ui_thread_(content::BrowserThread::UI, &message_loop_) {} virtual void SetUp() OVERRIDE { mock_file_system_.reset(new StrictMock); } virtual void TearDown() OVERRIDE { EXPECT_CALL(*mock_file_system_, RemoveObserver(_)); prefetcher_.reset(); mock_file_system_.reset(); } protected: // Sets a new prefetcher that fetches at most |prefetch_count| latest files. void InitPrefetcher(int prefetch_count) { EXPECT_CALL(*mock_file_system_, AddObserver(_)); DrivePrefetcherOptions options; options.initial_prefetch_count = prefetch_count; prefetcher_.reset(new DrivePrefetcher(mock_file_system_.get(), options)); } // Flushes all the pending tasks on the current thread. void RunMessageLoop() { base::RunLoop run_loop; run_loop.RunUntilIdle(); } // Verifies that fully running the prefetching loop over |test_entries| // correctly fetches the |expected| files, in the given order. void VerifyFullScan(const std::vector& test_entries, const std::vector& expected) { EXPECT_CALL(*mock_file_system_, ReadDirectoryByPath(_, _)) .WillRepeatedly(MockReadDirectory(test_entries)); EXPECT_CALL(*mock_file_system_, GetFileByResourceId(_, _, _)).Times(0); prefetcher_->OnInitialLoadFinished(DRIVE_FILE_OK); RunMessageLoop(); std::vector fetched_list; EXPECT_CALL(*mock_file_system_, GetFileByResourceId(_, _, _)) .WillRepeatedly(MockGetFile(&fetched_list)); prefetcher_->OnSyncClientIdle(); RunMessageLoop(); EXPECT_EQ(expected, fetched_list); } scoped_ptr > mock_file_system_; scoped_ptr prefetcher_; MessageLoopForUI message_loop_; content::TestBrowserThread ui_thread_; }; TEST_F(DrivePrefetcherTest, ZeroFiles) { InitPrefetcher(3); VerifyFullScan( std::vector(kEmptyDrive, kEmptyDrive + arraysize(kEmptyDrive)), std::vector()); } TEST_F(DrivePrefetcherTest, OneFile) { InitPrefetcher(3); VerifyFullScan( std::vector(kOneFileDrive, kOneFileDrive + arraysize(kOneFileDrive)), std::vector(kExpectedOneFile, kExpectedOneFile + arraysize(kExpectedOneFile))); } TEST_F(DrivePrefetcherTest, MoreThanLimitFiles) { // Files with the largest timestamps should be listed, in the order of time. // Directories nor hosted files should not be listed. InitPrefetcher(3); VerifyFullScan( std::vector(kComplexDrive, kComplexDrive + arraysize(kComplexDrive)), std::vector(kTop3Files, kTop3Files + arraysize(kTop3Files))); } TEST_F(DrivePrefetcherTest, DirectoryTraversal) { // Ensure the prefetcher correctly traverses whole the file system tree. // This is checked by setting the fetch limit larger than the number of files. InitPrefetcher(100); VerifyFullScan( std::vector(kComplexDrive, kComplexDrive + arraysize(kComplexDrive)), std::vector(kAllRegularFiles, kAllRegularFiles + arraysize(kAllRegularFiles))); } } // namespace drive