// 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/callback_helpers.h" #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 "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 "content/public/test/test_browser_thread_bundle.h" #include "google_apis/drive/test_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace drive { namespace internal { namespace { const char kCacheFileDirectory[] = "files"; } // namespace // 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(base::CreateDirectory(metadata_dir)); ASSERT_TRUE(base::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(base::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(base::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, Store) { 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("id"); std::string md5(base::MD5String(src_contents)); // Store a file. EXPECT_EQ(FILE_ERROR_OK, cache_->Store( id, md5, src_file_path, FileCache::FILE_OPERATION_COPY)); FileCacheEntry cache_entry; EXPECT_TRUE(cache_->GetCacheEntry(id, &cache_entry)); EXPECT_TRUE(cache_entry.is_present()); EXPECT_EQ(md5, cache_entry.md5()); base::FilePath cache_file_path; EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path)); EXPECT_TRUE(base::ContentsEqual(src_file_path, cache_file_path)); // Store a non-existent file. EXPECT_EQ(FILE_ERROR_FAILED, cache_->Store( id, md5, base::FilePath::FromUTF8Unsafe("non_existent_file"), FileCache::FILE_OPERATION_COPY)); // Passing empty MD5 marks the entry as dirty. EXPECT_EQ(FILE_ERROR_OK, cache_->Store( id, std::string(), src_file_path, FileCache::FILE_OPERATION_COPY)); EXPECT_TRUE(cache_->GetCacheEntry(id, &cache_entry)); EXPECT_TRUE(cache_entry.is_present()); EXPECT_TRUE(cache_entry.md5().empty()); EXPECT_TRUE(cache_entry.is_dirty()); // No free space available. fake_free_disk_space_getter_->set_default_value(0); EXPECT_EQ(FILE_ERROR_NO_LOCAL_SPACE, cache_->Store( id, md5, src_file_path, FileCache::FILE_OPERATION_COPY)); } TEST_F(FileCacheTest, PinAndUnpin) { 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("id_present"); std::string md5(base::MD5String(src_contents)); // Store a file. EXPECT_EQ(FILE_ERROR_OK, cache_->Store( id, md5, src_file_path, FileCache::FILE_OPERATION_COPY)); FileCacheEntry cache_entry; EXPECT_TRUE(cache_->GetCacheEntry(id, &cache_entry)); EXPECT_FALSE(cache_entry.is_pinned()); // Pin the existing file. EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(id)); EXPECT_TRUE(cache_->GetCacheEntry(id, &cache_entry)); EXPECT_TRUE(cache_entry.is_pinned()); // Unpin the file. EXPECT_EQ(FILE_ERROR_OK, cache_->Unpin(id)); EXPECT_TRUE(cache_->GetCacheEntry(id, &cache_entry)); EXPECT_FALSE(cache_entry.is_pinned()); // Pin a non-present file. std::string id_non_present = "id_non_present"; EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(id_non_present)); EXPECT_TRUE(cache_->GetCacheEntry(id_non_present, &cache_entry)); EXPECT_TRUE(cache_entry.is_pinned()); // Unpin the previously pinned non-existent file. EXPECT_EQ(FILE_ERROR_OK, cache_->Unpin(id_non_present)); EXPECT_FALSE(cache_->GetCacheEntry(id_non_present, &cache_entry)); // Unpin a file that doesn't exist in cache and is not pinned. EXPECT_EQ(FILE_ERROR_NOT_FOUND, cache_->Unpin("id_non_existent")); } TEST_F(FileCacheTest, MountUnmount) { 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("id_present"); std::string md5(base::MD5String(src_contents)); // Store a file. EXPECT_EQ(FILE_ERROR_OK, cache_->Store( id, md5, src_file_path, FileCache::FILE_OPERATION_COPY)); // Mark the file mounted. base::FilePath cache_file_path; EXPECT_EQ(FILE_ERROR_OK, cache_->MarkAsMounted(id, &cache_file_path)); // Try to remove it. EXPECT_EQ(FILE_ERROR_IN_USE, cache_->Remove(id)); // Clear mounted state of the file. EXPECT_EQ(FILE_ERROR_OK, cache_->MarkAsUnmounted(cache_file_path)); // Try to remove again. EXPECT_EQ(FILE_ERROR_OK, cache_->Remove(id)); } TEST_F(FileCacheTest, OpenForWrite) { // Prepare a file. base::FilePath src_file; ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file)); const std::string id = "id"; ASSERT_EQ(FILE_ERROR_OK, cache_->Store(id, "md5", src_file, FileCache::FILE_OPERATION_COPY)); // Entry is not dirty nor opened. EXPECT_FALSE(cache_->IsOpenedForWrite(id)); FileCacheEntry entry; EXPECT_TRUE(cache_->GetCacheEntry(id, &entry)); EXPECT_FALSE(entry.is_dirty()); // Open (1). scoped_ptr file_closer1; EXPECT_EQ(FILE_ERROR_OK, cache_->OpenForWrite(id, &file_closer1)); EXPECT_TRUE(cache_->IsOpenedForWrite(id)); // Entry is dirty. EXPECT_TRUE(cache_->GetCacheEntry(id, &entry)); EXPECT_TRUE(entry.is_dirty()); // Open (2). scoped_ptr file_closer2; EXPECT_EQ(FILE_ERROR_OK, cache_->OpenForWrite(id, &file_closer2)); EXPECT_TRUE(cache_->IsOpenedForWrite(id)); // Close (1). file_closer1.reset(); EXPECT_TRUE(cache_->IsOpenedForWrite(id)); // Close (2). file_closer2.reset(); EXPECT_FALSE(cache_->IsOpenedForWrite(id)); // Try to open non-existent file. EXPECT_EQ(FILE_ERROR_NOT_FOUND, cache_->OpenForWrite("nonexistent_id", &file_closer1)); } TEST_F(FileCacheTest, UpdateMd5) { // Store test data. const base::FilePath src_file_path = temp_dir_.path().Append("test.dat"); const std::string contents_before = "before"; EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path, contents_before)); std::string id("id1"); EXPECT_EQ(FILE_ERROR_OK, cache_->Store(id, base::MD5String(contents_before), src_file_path, FileCache::FILE_OPERATION_COPY)); // Modify the cache file. scoped_ptr file_closer; EXPECT_EQ(FILE_ERROR_OK, cache_->OpenForWrite(id, &file_closer)); base::FilePath cache_file_path; EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path)); const std::string contents_after = "after"; EXPECT_TRUE(google_apis::test_util::WriteStringToFile(cache_file_path, contents_after)); // Cannot update MD5 of an opend file. EXPECT_EQ(FILE_ERROR_IN_USE, cache_->UpdateMd5(id)); // Close file. file_closer.reset(); // MD5 was cleared by OpenForWrite(). FileCacheEntry entry; EXPECT_TRUE(cache_->GetCacheEntry(id, &entry)); EXPECT_TRUE(entry.md5().empty()); // Update MD5. EXPECT_EQ(FILE_ERROR_OK, cache_->UpdateMd5(id)); EXPECT_TRUE(cache_->GetCacheEntry(id, &entry)); EXPECT_EQ(base::MD5String(contents_after), entry.md5()); } TEST_F(FileCacheTest, ClearDirty) { // Prepare a file. base::FilePath src_file; ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file)); const std::string id = "id"; ASSERT_EQ(FILE_ERROR_OK, cache_->Store(id, "md5", src_file, FileCache::FILE_OPERATION_COPY)); // Open the file. scoped_ptr file_closer; EXPECT_EQ(FILE_ERROR_OK, cache_->OpenForWrite(id, &file_closer)); // Entry is dirty. FileCacheEntry entry; EXPECT_TRUE(cache_->GetCacheEntry(id, &entry)); EXPECT_TRUE(entry.is_dirty()); // Cannot clear the dirty bit of an opened entry. EXPECT_EQ(FILE_ERROR_IN_USE, cache_->ClearDirty(id)); // Close the file and clear the dirty bit. file_closer.reset(); EXPECT_EQ(FILE_ERROR_OK, cache_->ClearDirty(id)); // Entry is not dirty. EXPECT_TRUE(cache_->GetCacheEntry(id, &entry)); EXPECT_FALSE(entry.is_dirty()); } TEST_F(FileCacheTest, Remove) { 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("id"); std::string md5(base::MD5String(src_contents)); // First store a file to cache. base::FilePath src_file; ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file)); 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)); // Then try to remove existing file from cache. EXPECT_EQ(FILE_ERROR_OK, cache_->Remove(id)); EXPECT_FALSE(base::PathExists(cache_file_path)); } 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(base::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