// 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/file_cache.h" #include #include #include "base/file_util.h" #include "base/files/file_enumerator.h" #include "base/files/scoped_temp_dir.h" #include "base/md5.h" #include "base/path_service.h" #include "base/run_loop.h" #include "base/threading/sequenced_worker_pool.h" #include "chrome/browser/chromeos/drive/drive.pb.h" #include "chrome/browser/chromeos/drive/fake_free_disk_space_getter.h" #include "chrome/browser/chromeos/drive/file_system_util.h" #include "chrome/browser/chromeos/drive/resource_metadata_storage.h" #include "chrome/browser/chromeos/drive/test_util.h" #include "chrome/browser/google_apis/test_util.h" #include "content/public/browser/browser_thread.h" #include "content/public/test/test_browser_thread_bundle.h" #include "testing/gtest/include/gtest/gtest.h" namespace drive { namespace internal { namespace { const char kCacheFileDirectory[] = "files"; // Bitmask of cache states in FileCacheEntry. enum TestFileCacheState { TEST_CACHE_STATE_NONE = 0, TEST_CACHE_STATE_PINNED = 1 << 0, TEST_CACHE_STATE_PRESENT = 1 << 1, TEST_CACHE_STATE_DIRTY = 1 << 2, }; } // namespace // Tests FileCache methods from UI thread. It internally uses a real blocking // pool and tests the interaction among threads. // TODO(hashimoto): remove this class. crbug.com/231221. class FileCacheTestOnUIThread : public testing::Test { protected: FileCacheTestOnUIThread() : expected_error_(FILE_ERROR_OK), expected_cache_state_(0) { } virtual void SetUp() OVERRIDE { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); const base::FilePath metadata_dir = temp_dir_.path().AppendASCII("meta"); const base::FilePath cache_dir = temp_dir_.path().AppendASCII(kCacheFileDirectory); ASSERT_TRUE(file_util::CreateDirectory(metadata_dir)); ASSERT_TRUE(file_util::CreateDirectory(cache_dir)); ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &dummy_file_path_)); fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter); scoped_refptr pool = content::BrowserThread::GetBlockingPool(); blocking_task_runner_ = pool->GetSequencedTaskRunner(pool->GetSequenceToken()); metadata_storage_.reset(new ResourceMetadataStorage( metadata_dir, blocking_task_runner_.get())); bool success = false; base::PostTaskAndReplyWithResult( blocking_task_runner_.get(), FROM_HERE, base::Bind(&ResourceMetadataStorage::Initialize, base::Unretained(metadata_storage_.get())), google_apis::test_util::CreateCopyResultCallback(&success)); test_util::RunBlockingPoolTask(); ASSERT_TRUE(success); cache_.reset(new FileCache( metadata_storage_.get(), cache_dir, blocking_task_runner_.get(), fake_free_disk_space_getter_.get())); success = false; base::PostTaskAndReplyWithResult( blocking_task_runner_.get(), FROM_HERE, base::Bind(&FileCache::Initialize, base::Unretained(cache_.get())), google_apis::test_util::CreateCopyResultCallback(&success)); test_util::RunBlockingPoolTask(); ASSERT_TRUE(success); } void TestStoreToCache(const std::string& id, const std::string& md5, const base::FilePath& source_path, FileError expected_error, int expected_cache_state) { expected_error_ = expected_error; expected_cache_state_ = expected_cache_state; FileError error = FILE_ERROR_OK; base::PostTaskAndReplyWithResult( blocking_task_runner_, FROM_HERE, base::Bind(&internal::FileCache::Store, base::Unretained(cache_.get()), id, md5, source_path, FileCache::FILE_OPERATION_COPY), google_apis::test_util::CreateCopyResultCallback(&error)); test_util::RunBlockingPoolTask(); if (error == FILE_ERROR_OK) { FileCacheEntry cache_entry; EXPECT_TRUE(GetCacheEntryFromOriginThread(id, &cache_entry)); EXPECT_EQ(md5, cache_entry.md5()); } VerifyCacheFileState(error, id); } void TestRemoveFromCache(const std::string& id, FileError expected_error) { expected_error_ = expected_error; FileError error = FILE_ERROR_OK; base::PostTaskAndReplyWithResult( blocking_task_runner_, FROM_HERE, base::Bind(&internal::FileCache::Remove, base::Unretained(cache_.get()), id), google_apis::test_util::CreateCopyResultCallback(&error)); test_util::RunBlockingPoolTask(); VerifyRemoveFromCache(error, id); } void VerifyRemoveFromCache(FileError error, const std::string& id) { EXPECT_EQ(expected_error_, error); FileCacheEntry cache_entry; if (!GetCacheEntryFromOriginThread(id, &cache_entry)) { EXPECT_EQ(FILE_ERROR_OK, error); const base::FilePath path = cache_->GetCacheFilePath(id); EXPECT_FALSE(base::PathExists(path)); } } void TestPin(const std::string& id, FileError expected_error, int expected_cache_state) { expected_error_ = expected_error; expected_cache_state_ = expected_cache_state; FileError error = FILE_ERROR_OK; cache_->PinOnUIThread( id, google_apis::test_util::CreateCopyResultCallback(&error)); test_util::RunBlockingPoolTask(); VerifyCacheFileState(error, id); } void TestUnpin(const std::string& id, FileError expected_error, int expected_cache_state) { expected_error_ = expected_error; expected_cache_state_ = expected_cache_state; FileError error = FILE_ERROR_OK; cache_->UnpinOnUIThread( id, google_apis::test_util::CreateCopyResultCallback(&error)); test_util::RunBlockingPoolTask(); VerifyCacheFileState(error, id); } void TestMarkDirty(const std::string& id, FileError expected_error, int expected_cache_state) { expected_error_ = expected_error; expected_cache_state_ = expected_cache_state; FileError error = FILE_ERROR_OK; base::PostTaskAndReplyWithResult( blocking_task_runner_, FROM_HERE, base::Bind(&internal::FileCache::MarkDirty, base::Unretained(cache_.get()), id), google_apis::test_util::CreateCopyResultCallback(&error)); test_util::RunBlockingPoolTask(); VerifyCacheFileState(error, id); // Verify filename. if (error == FILE_ERROR_OK) { base::FilePath cache_file_path; base::PostTaskAndReplyWithResult( blocking_task_runner_, FROM_HERE, base::Bind(&FileCache::GetFile, base::Unretained(cache_.get()), id, &cache_file_path), google_apis::test_util::CreateCopyResultCallback(&error)); test_util::RunBlockingPoolTask(); EXPECT_EQ(FILE_ERROR_OK, error); EXPECT_EQ(util::EscapeCacheFileName(id), cache_file_path.BaseName().AsUTF8Unsafe()); } } void TestClearDirty(const std::string& id, const std::string& md5, FileError expected_error, int expected_cache_state) { expected_error_ = expected_error; expected_cache_state_ = expected_cache_state; FileError error = FILE_ERROR_OK; PostTaskAndReplyWithResult( blocking_task_runner_.get(), FROM_HERE, base::Bind(&FileCache::ClearDirty, base::Unretained(cache_.get()), id, md5), google_apis::test_util::CreateCopyResultCallback(&error)); test_util::RunBlockingPoolTask(); if (error == FILE_ERROR_OK) { FileCacheEntry cache_entry; EXPECT_TRUE(GetCacheEntryFromOriginThread(id, &cache_entry)); EXPECT_EQ(md5, cache_entry.md5()); } VerifyCacheFileState(error, id); } void TestMarkAsMounted(const std::string& id, FileError expected_error, int expected_cache_state) { expected_error_ = expected_error; expected_cache_state_ = expected_cache_state; FileCacheEntry entry; EXPECT_TRUE(GetCacheEntryFromOriginThread(id, &entry)); FileError error = FILE_ERROR_OK; base::FilePath cache_file_path; cache_->MarkAsMountedOnUIThread( id, google_apis::test_util::CreateCopyResultCallback( &error, &cache_file_path)); test_util::RunBlockingPoolTask(); EXPECT_TRUE(base::PathExists(cache_file_path)); EXPECT_EQ(cache_file_path, cache_->GetCacheFilePath(id)); } void TestMarkAsUnmounted(const std::string& id, const base::FilePath& file_path, FileError expected_error, int expected_cache_state) { expected_error_ = expected_error; expected_cache_state_ = expected_cache_state; FileError error = FILE_ERROR_OK; cache_->MarkAsUnmountedOnUIThread( file_path, google_apis::test_util::CreateCopyResultCallback(&error)); test_util::RunBlockingPoolTask(); base::FilePath cache_file_path; base::PostTaskAndReplyWithResult( blocking_task_runner_, FROM_HERE, base::Bind(&FileCache::GetFile, base::Unretained(cache_.get()), id, &cache_file_path), google_apis::test_util::CreateCopyResultCallback(&error)); test_util::RunBlockingPoolTask(); EXPECT_EQ(FILE_ERROR_OK, error); EXPECT_TRUE(base::PathExists(cache_file_path)); EXPECT_EQ(cache_file_path, cache_->GetCacheFilePath(id)); } void VerifyCacheFileState(FileError error, const std::string& id) { EXPECT_EQ(expected_error_, error); // Verify cache map. FileCacheEntry cache_entry; const bool cache_entry_found = GetCacheEntryFromOriginThread(id, &cache_entry); if ((expected_cache_state_ & TEST_CACHE_STATE_PRESENT) || (expected_cache_state_ & TEST_CACHE_STATE_PINNED)) { ASSERT_TRUE(cache_entry_found); EXPECT_EQ((expected_cache_state_ & TEST_CACHE_STATE_PINNED) != 0, cache_entry.is_pinned()); EXPECT_EQ((expected_cache_state_ & TEST_CACHE_STATE_PRESENT) != 0, cache_entry.is_present()); EXPECT_EQ((expected_cache_state_ & TEST_CACHE_STATE_DIRTY) != 0, cache_entry.is_dirty()); } else { EXPECT_FALSE(cache_entry_found); } // Verify actual cache file. base::FilePath dest_path = cache_->GetCacheFilePath(id); EXPECT_EQ((expected_cache_state_ & TEST_CACHE_STATE_PRESENT) != 0, base::PathExists(dest_path)); } // Helper function to call GetCacheEntry from origin thread. bool GetCacheEntryFromOriginThread(const std::string& id, FileCacheEntry* cache_entry) { bool result = false; base::PostTaskAndReplyWithResult( blocking_task_runner_, FROM_HERE, base::Bind(&internal::FileCache::GetCacheEntry, base::Unretained(cache_.get()), id, cache_entry), google_apis::test_util::CreateCopyResultCallback(&result)); test_util::RunBlockingPoolTask(); return result; } // Returns true if the cache entry exists for the given ID. bool CacheEntryExists(const std::string& id) { FileCacheEntry cache_entry; return GetCacheEntryFromOriginThread(id, &cache_entry); } // Returns the number of the cache files with name , and Confirm // that they have the . This should return 1 or 0. size_t CountCacheFiles(const std::string& id, const std::string& md5) { base::FilePath path = cache_->GetCacheFilePath(id); base::FileEnumerator enumerator(path.DirName(), false, // recursive base::FileEnumerator::FILES, path.BaseName().value()); size_t num_files_found = 0; for (base::FilePath current = enumerator.Next(); !current.empty(); current = enumerator.Next()) { ++num_files_found; EXPECT_EQ(util::EscapeCacheFileName(id), current.BaseName().AsUTF8Unsafe()); } return num_files_found; } content::TestBrowserThreadBundle thread_bundle_; scoped_refptr blocking_task_runner_; base::ScopedTempDir temp_dir_; base::FilePath dummy_file_path_; scoped_ptr metadata_storage_; scoped_ptr cache_; scoped_ptr fake_free_disk_space_getter_; FileError expected_error_; int expected_cache_state_; std::string expected_file_extension_; }; TEST_F(FileCacheTestOnUIThread, StoreToCacheSimple) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // Store an existing file. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); // Store a non-existent file to the same |id| and |md5|. TestStoreToCache(id, md5, base::FilePath::FromUTF8Unsafe("non_existent_file"), FILE_ERROR_FAILED, TEST_CACHE_STATE_PRESENT); // Store a different existing file to the same |id| but different // |md5|. md5 = "new_md5"; TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); // Verify that there's only one file with name , i.e. previously // cached file with the different md5 should be deleted. EXPECT_EQ(1U, CountCacheFiles(id, md5)); } TEST_F(FileCacheTestOnUIThread, RemoveFromCacheSimple) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // First store a file to cache. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); // Then try to remove existing file from cache. TestRemoveFromCache(id, FILE_ERROR_OK); // Repeat using non-alphanumeric characters for ID, including '.' // which is an extension separator. id = "pdf:`~!@#$%^&*()-_=+[{|]}\\;',<.>/?"; TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); TestRemoveFromCache(id, FILE_ERROR_OK); } TEST_F(FileCacheTestOnUIThread, PinAndUnpin) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // First store a file to cache. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); // Pin the existing file in cache. TestPin(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); // Unpin the existing file in cache. TestUnpin(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); // Pin back the same existing file in cache. TestPin(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); // Pin a non-existent file in cache. id = "document:1a2b"; TestPin(id, FILE_ERROR_OK, TEST_CACHE_STATE_PINNED); // Unpin the previously pinned non-existent file in cache. TestUnpin(id, FILE_ERROR_OK, TEST_CACHE_STATE_NONE); // Unpin a file that doesn't exist in cache and is not pinned, i.e. cache // has zero knowledge of the file. id = "not-in-cache:1a2b"; TestUnpin(id, FILE_ERROR_NOT_FOUND, TEST_CACHE_STATE_NONE); } TEST_F(FileCacheTestOnUIThread, StoreToCachePinned) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // Pin a non-existent file. TestPin(id, FILE_ERROR_OK, TEST_CACHE_STATE_PINNED); // Store an existing file to a previously pinned file. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); // Store a non-existent file to a previously pinned and stored file. TestStoreToCache(id, md5, base::FilePath::FromUTF8Unsafe("non_existent_file"), FILE_ERROR_FAILED, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); } TEST_F(FileCacheTestOnUIThread, RemoveFromCachePinned) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // Store a file to cache, and pin it. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); TestPin(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); // Remove |id| from cache. TestRemoveFromCache(id, FILE_ERROR_OK); // Use non-alphanumeric characters for ID, including '.' // which is an extension separator. id = "pdf:`~!@#$%^&*()-_=+[{|]}\\;',<.>/?"; TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); TestPin(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); TestRemoveFromCache(id, FILE_ERROR_OK); } TEST_F(FileCacheTestOnUIThread, DirtyCacheSimple) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // First store a file to cache. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); // Mark the file dirty. TestMarkDirty(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); // Clear dirty state of the file. TestClearDirty(id, md5, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); } TEST_F(FileCacheTestOnUIThread, DirtyCachePinned) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // First store a file to cache and pin it. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); TestPin(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); // Mark the file dirty. TestMarkDirty(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY | TEST_CACHE_STATE_PINNED); // Clear dirty state of the file. TestClearDirty(id, md5, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); } TEST_F(FileCacheTestOnUIThread, PinAndUnpinDirtyCache) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // First store a file to cache and mark it as dirty. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); TestMarkDirty(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); // Verifies dirty file exists. base::FilePath dirty_path; FileError error = FILE_ERROR_FAILED; base::PostTaskAndReplyWithResult( blocking_task_runner_, FROM_HERE, base::Bind(&FileCache::GetFile, base::Unretained(cache_.get()), id, &dirty_path), google_apis::test_util::CreateCopyResultCallback(&error)); test_util::RunBlockingPoolTask(); EXPECT_EQ(FILE_ERROR_OK, error); EXPECT_TRUE(base::PathExists(dirty_path)); // Pin the dirty file. TestPin(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY | TEST_CACHE_STATE_PINNED); // Verify dirty file still exist at the same pathname. EXPECT_TRUE(base::PathExists(dirty_path)); // Unpin the dirty file. TestUnpin(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); // Verify dirty file still exist at the same pathname. EXPECT_TRUE(base::PathExists(dirty_path)); } TEST_F(FileCacheTestOnUIThread, DirtyCacheRepetitive) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // First store a file to cache. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); // Mark the file dirty. TestMarkDirty(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); // Again, mark the file dirty. Nothing should change. TestMarkDirty(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); // Clear dirty state of the file. TestClearDirty(id, md5, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); // Again, clear dirty state of the file, which is no longer dirty. TestClearDirty(id, md5, FILE_ERROR_INVALID_OPERATION, TEST_CACHE_STATE_PRESENT); } TEST_F(FileCacheTestOnUIThread, DirtyCacheInvalid) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // Mark a non-existent file dirty. TestMarkDirty(id, FILE_ERROR_NOT_FOUND, TEST_CACHE_STATE_NONE); // Clear dirty state of a non-existent file. TestClearDirty(id, md5, FILE_ERROR_NOT_FOUND, TEST_CACHE_STATE_NONE); // Store a file to cache. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); // Clear dirty state of a non-dirty existing file. TestClearDirty(id, md5, FILE_ERROR_INVALID_OPERATION, TEST_CACHE_STATE_PRESENT); // Mark an existing file dirty, then store a new file to the same ID // but different md5, which should fail. TestMarkDirty(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); md5 = "new_md5"; TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_IN_USE, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); } TEST_F(FileCacheTestOnUIThread, RemoveFromDirtyCache) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // Store a file to cache, pin it, mark it dirty and commit it. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); TestPin(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); TestMarkDirty(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED | TEST_CACHE_STATE_DIRTY); // Try to remove the file. Dirty caches can be removed at the level of // FileCache::Remove. Upper layer cache clearance functions like // FreeDiskSpaceIfNeededFor() and RemoveStaleCacheFiles() takes care of // securing dirty files. TestRemoveFromCache(id, FILE_ERROR_OK); } TEST_F(FileCacheTestOnUIThread, MountUnmount) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // First store a file to cache. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); // Mark the file mounted. TestMarkAsMounted(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); EXPECT_TRUE(CacheEntryExists(id)); // Try to remove the file. TestRemoveFromCache(id, FILE_ERROR_IN_USE); // Clear mounted state of the file. base::FilePath file_path; FileError error = FILE_ERROR_FAILED; base::PostTaskAndReplyWithResult( blocking_task_runner_, FROM_HERE, base::Bind(&FileCache::GetFile, base::Unretained(cache_.get()), id, &file_path), google_apis::test_util::CreateCopyResultCallback(&error)); test_util::RunBlockingPoolTask(); EXPECT_EQ(FILE_ERROR_OK, error); TestMarkAsUnmounted(id, file_path, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); EXPECT_TRUE(CacheEntryExists(id)); // Try to remove the file. TestRemoveFromCache(id, FILE_ERROR_OK); } TEST_F(FileCacheTestOnUIThread, StoreToCacheNoSpace) { fake_free_disk_space_getter_->set_default_value(0); std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); // Try to store an existing file. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_NO_LOCAL_SPACE, TEST_CACHE_STATE_NONE); // Verify that there's no files added. EXPECT_EQ(0U, CountCacheFiles(id, md5)); } TEST_F(FileCacheTestOnUIThread, UpdatePinnedCache) { std::string id("pdf:1a2b"); std::string md5("abcdef0123456789"); std::string md5_modified("aaaaaa0000000000"); // Store an existing file. TestStoreToCache(id, md5, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); // Pin the file. TestPin(id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); // Store the file with a modified content and md5. It should stay pinned. TestStoreToCache(id, md5_modified, dummy_file_path_, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); } // Tests FileCache methods working with the blocking task runner. class FileCacheTest : public testing::Test { protected: virtual void SetUp() OVERRIDE { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); const base::FilePath metadata_dir = temp_dir_.path().AppendASCII("meta"); cache_files_dir_ = temp_dir_.path().AppendASCII(kCacheFileDirectory); ASSERT_TRUE(file_util::CreateDirectory(metadata_dir)); ASSERT_TRUE(file_util::CreateDirectory(cache_files_dir_)); fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter); metadata_storage_.reset(new ResourceMetadataStorage( metadata_dir, base::MessageLoopProxy::current().get())); ASSERT_TRUE(metadata_storage_->Initialize()); cache_.reset(new FileCache( metadata_storage_.get(), cache_files_dir_, base::MessageLoopProxy::current().get(), fake_free_disk_space_getter_.get())); ASSERT_TRUE(cache_->Initialize()); } static bool RenameCacheFilesToNewFormat(FileCache* cache) { return cache->RenameCacheFilesToNewFormat(); } content::TestBrowserThreadBundle thread_bundle_; base::ScopedTempDir temp_dir_; base::FilePath cache_files_dir_; scoped_ptr metadata_storage_; scoped_ptr cache_; scoped_ptr fake_free_disk_space_getter_; }; TEST_F(FileCacheTest, RecoverFilesFromCacheDirectory) { base::FilePath dir_source_root; EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &dir_source_root)); const base::FilePath src_path = dir_source_root.AppendASCII("chrome/test/data/chromeos/drive/image.png"); // Store files. This file should not be moved. EXPECT_EQ(FILE_ERROR_OK, cache_->Store("id_foo", "md5", src_path, FileCache::FILE_OPERATION_COPY)); // Set up files in the cache directory. These files should be moved. const base::FilePath file_directory = temp_dir_.path().AppendASCII(kCacheFileDirectory); ASSERT_TRUE(base::CopyFile(src_path, file_directory.AppendASCII("id_bar"))); ASSERT_TRUE(base::CopyFile(src_path, file_directory.AppendASCII("id_baz"))); // Insert a dirty entry with "id_baz" to |recovered_cache_info|. // This should not prevent the file from being recovered. ResourceMetadataStorage::RecoveredCacheInfoMap recovered_cache_info; recovered_cache_info["id_baz"].is_dirty = true; recovered_cache_info["id_baz"].title = "baz.png"; // Recover files. const base::FilePath dest_directory = temp_dir_.path().AppendASCII("dest"); EXPECT_TRUE(cache_->RecoverFilesFromCacheDirectory(dest_directory, recovered_cache_info)); // Only two files should be recovered. EXPECT_TRUE(base::PathExists(dest_directory)); // base::FileEnumerator does not guarantee the order. if (base::PathExists(dest_directory.AppendASCII("baz00000001.png"))) { EXPECT_TRUE(base::ContentsEqual( src_path, dest_directory.AppendASCII("baz00000001.png"))); EXPECT_TRUE(base::ContentsEqual( src_path, dest_directory.AppendASCII("image00000002.png"))); } else { EXPECT_TRUE(base::ContentsEqual( src_path, dest_directory.AppendASCII("image00000001.png"))); EXPECT_TRUE(base::ContentsEqual( src_path, dest_directory.AppendASCII("baz00000002.png"))); } EXPECT_FALSE(base::PathExists( dest_directory.AppendASCII("image00000003.png"))); } TEST_F(FileCacheTest, Iterator) { base::FilePath src_file; ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &src_file)); // Prepare entries. std::map md5s; md5s["id1"] = "md5-1"; md5s["id2"] = "md5-2"; md5s["id3"] = "md5-3"; md5s["id4"] = "md5-4"; for (std::map::iterator it = md5s.begin(); it != md5s.end(); ++it) { EXPECT_EQ(FILE_ERROR_OK, cache_->Store( it->first, it->second, src_file, FileCache::FILE_OPERATION_COPY)); } // Iterate. std::map result; scoped_ptr it = cache_->GetIterator(); for (; !it->IsAtEnd(); it->Advance()) result[it->GetID()] = it->GetValue().md5(); EXPECT_EQ(md5s, result); EXPECT_FALSE(it->HasError()); } TEST_F(FileCacheTest, FreeDiskSpaceIfNeededFor) { base::FilePath src_file; ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &src_file)); // Store a file as a 'temporary' file and remember the path. const std::string id_tmp = "id_tmp", md5_tmp = "md5_tmp"; ASSERT_EQ(FILE_ERROR_OK, cache_->Store(id_tmp, md5_tmp, src_file, FileCache::FILE_OPERATION_COPY)); base::FilePath tmp_path; ASSERT_EQ(FILE_ERROR_OK, cache_->GetFile(id_tmp, &tmp_path)); // Store a file as a pinned file and remember the path. const std::string id_pinned = "id_pinned", md5_pinned = "md5_pinned"; ASSERT_EQ(FILE_ERROR_OK, cache_->Store(id_pinned, md5_pinned, src_file, FileCache::FILE_OPERATION_COPY)); ASSERT_EQ(FILE_ERROR_OK, cache_->Pin(id_pinned)); base::FilePath pinned_path; ASSERT_EQ(FILE_ERROR_OK, cache_->GetFile(id_pinned, &pinned_path)); // Call FreeDiskSpaceIfNeededFor(). fake_free_disk_space_getter_->set_default_value(test_util::kLotsOfSpace); fake_free_disk_space_getter_->PushFakeValue(0); const int64 kNeededBytes = 1; EXPECT_TRUE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes)); // Only 'temporary' file gets removed. FileCacheEntry entry; EXPECT_FALSE(cache_->GetCacheEntry(id_tmp, &entry)); EXPECT_FALSE(base::PathExists(tmp_path)); EXPECT_TRUE(cache_->GetCacheEntry(id_pinned, &entry)); EXPECT_TRUE(base::PathExists(pinned_path)); // Returns false when disk space cannot be freed. fake_free_disk_space_getter_->set_default_value(0); EXPECT_FALSE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes)); } TEST_F(FileCacheTest, GetFile) { const base::FilePath src_file_path = temp_dir_.path().Append("test.dat"); const std::string src_contents = "test"; EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path, src_contents)); std::string id("id1"); std::string md5(base::MD5String(src_contents)); const base::FilePath cache_file_directory = temp_dir_.path().AppendASCII(kCacheFileDirectory); // Try to get an existing file from cache. EXPECT_EQ(FILE_ERROR_OK, cache_->Store(id, md5, src_file_path, FileCache::FILE_OPERATION_COPY)); base::FilePath cache_file_path; EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path)); EXPECT_EQ( cache_file_directory.AppendASCII(util::EscapeCacheFileName(id)).value(), cache_file_path.value()); std::string contents; EXPECT_TRUE(base::ReadFileToString(cache_file_path, &contents)); EXPECT_EQ(src_contents, contents); // Get file from cache with different id. id = "id2"; EXPECT_EQ(FILE_ERROR_NOT_FOUND, cache_->GetFile(id, &cache_file_path)); // Pin a non-existent file. EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(id)); // Get the non-existent pinned file from cache. EXPECT_EQ(FILE_ERROR_NOT_FOUND, cache_->GetFile(id, &cache_file_path)); // Get a previously pinned and stored file from cache. EXPECT_EQ(FILE_ERROR_OK, cache_->Store(id, md5, src_file_path, FileCache::FILE_OPERATION_COPY)); EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path)); EXPECT_EQ( cache_file_directory.AppendASCII(util::EscapeCacheFileName(id)).value(), cache_file_path.value()); contents.clear(); EXPECT_TRUE(base::ReadFileToString(cache_file_path, &contents)); EXPECT_EQ(src_contents, contents); } TEST_F(FileCacheTest, RenameCacheFilesToNewFormat) { const base::FilePath file_directory = temp_dir_.path().AppendASCII(kCacheFileDirectory); // File with an old style ":." name. ASSERT_TRUE(google_apis::test_util::WriteStringToFile( file_directory.AppendASCII("file:id_koo.md5"), "koo")); // File with multiple extensions should be removed. ASSERT_TRUE(google_apis::test_util::WriteStringToFile( file_directory.AppendASCII("id_kyu.md5.mounted"), "kyu (mounted)")); ASSERT_TRUE(google_apis::test_util::WriteStringToFile( file_directory.AppendASCII("id_kyu.md5"), "kyu")); // Rename and verify the result. EXPECT_TRUE(RenameCacheFilesToNewFormat(cache_.get())); std::string contents; EXPECT_TRUE(base::ReadFileToString(file_directory.AppendASCII("id_koo"), &contents)); EXPECT_EQ("koo", contents); contents.clear(); EXPECT_TRUE(base::ReadFileToString(file_directory.AppendASCII("id_kyu"), &contents)); EXPECT_EQ("kyu", contents); // Rename again. EXPECT_TRUE(RenameCacheFilesToNewFormat(cache_.get())); // Files with new style names are not affected. contents.clear(); EXPECT_TRUE(base::ReadFileToString(file_directory.AppendASCII("id_koo"), &contents)); EXPECT_EQ("koo", contents); contents.clear(); EXPECT_TRUE(base::ReadFileToString(file_directory.AppendASCII("id_kyu"), &contents)); EXPECT_EQ("kyu", contents); } TEST_F(FileCacheTest, ClearAll) { const std::string id("pdf:1a2b"); const std::string md5("abcdef0123456789"); // Store an existing file. base::FilePath src_file; ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &src_file)); ASSERT_EQ(FILE_ERROR_OK, cache_->Store(id, md5, src_file, FileCache::FILE_OPERATION_COPY)); // Verify that the cache entry is created. FileCacheEntry cache_entry; ASSERT_TRUE(cache_->GetCacheEntry(id, &cache_entry)); // Clear cache. EXPECT_TRUE(cache_->ClearAll()); // Verify that the cache is removed. EXPECT_FALSE(cache_->GetCacheEntry(id, &cache_entry)); EXPECT_TRUE(base::IsDirectoryEmpty(cache_files_dir_)); } } // namespace internal } // namespace drive