| // 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 <string> |
| #include <vector> |
| |
| #include "base/file_util.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/md5.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<base::SequencedWorkerPool> 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 <id>, and Confirm |
| // that they have the <md5>. 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<base::SequencedTaskRunner> blocking_task_runner_; |
| base::ScopedTempDir temp_dir_; |
| base::FilePath dummy_file_path_; |
| |
| scoped_ptr<ResourceMetadataStorage, test_util::DestroyHelperForTests> |
| metadata_storage_; |
| scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_; |
| scoped_ptr<FakeFreeDiskSpaceGetter> 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 <id>, 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<ResourceMetadataStorage, test_util::DestroyHelperForTests> |
| metadata_storage_; |
| scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_; |
| scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_; |
| }; |
| |
| TEST_F(FileCacheTest, ScanCacheFile) { |
| // Set up files in the cache directory. |
| const base::FilePath file_directory = |
| temp_dir_.path().AppendASCII(kCacheFileDirectory); |
| ASSERT_TRUE(google_apis::test_util::WriteStringToFile( |
| file_directory.AppendASCII("id_foo"), "foo")); |
| |
| // Remove the existing DB. |
| const base::FilePath metadata_directory = |
| temp_dir_.path().AppendASCII("meta"); |
| ASSERT_TRUE(base::DeleteFile(metadata_directory, true /* recursive */)); |
| |
| // Create a new cache and initialize it. |
| metadata_storage_.reset(new ResourceMetadataStorage( |
| metadata_directory, base::MessageLoopProxy::current().get())); |
| ASSERT_TRUE(metadata_storage_->Initialize()); |
| |
| cache_.reset(new FileCache(metadata_storage_.get(), |
| file_directory, |
| base::MessageLoopProxy::current().get(), |
| fake_free_disk_space_getter_.get())); |
| ASSERT_TRUE(cache_->Initialize()); |
| |
| // Check contents of the cache. |
| FileCacheEntry cache_entry; |
| EXPECT_TRUE(cache_->GetCacheEntry("id_foo", &cache_entry)); |
| EXPECT_TRUE(cache_entry.is_present()); |
| EXPECT_EQ(base::MD5String("foo"), cache_entry.md5()); |
| } |
| |
| TEST_F(FileCacheTest, Iterator) { |
| base::FilePath src_file; |
| ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &src_file)); |
| |
| // Prepare entries. |
| std::map<std::string, std::string> md5s; |
| md5s["id1"] = "md5-1"; |
| md5s["id2"] = "md5-2"; |
| md5s["id3"] = "md5-3"; |
| md5s["id4"] = "md5-4"; |
| for (std::map<std::string, std::string>::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<std::string, std::string> result; |
| scoped_ptr<FileCache::Iterator> 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 "<prefix>:<ID>.<MD5>" 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(file_util::IsDirectoryEmpty(cache_files_dir_)); |
| } |
| |
| } // namespace internal |
| } // namespace drive |