file_cache_unittest.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/chromeos/drive/file_cache.h" 6 7#include <string> 8#include <vector> 9 10#include "base/file_util.h" 11#include "base/files/file_enumerator.h" 12#include "base/files/scoped_temp_dir.h" 13#include "base/md5.h" 14#include "base/run_loop.h" 15#include "base/threading/sequenced_worker_pool.h" 16#include "chrome/browser/chromeos/drive/drive.pb.h" 17#include "chrome/browser/chromeos/drive/fake_free_disk_space_getter.h" 18#include "chrome/browser/chromeos/drive/file_cache_metadata.h" 19#include "chrome/browser/chromeos/drive/file_system_util.h" 20#include "chrome/browser/chromeos/drive/resource_metadata_storage.h" 21#include "chrome/browser/chromeos/drive/test_util.h" 22#include "chrome/browser/google_apis/test_util.h" 23#include "content/public/browser/browser_thread.h" 24#include "content/public/test/test_browser_thread_bundle.h" 25#include "testing/gtest/include/gtest/gtest.h" 26 27namespace drive { 28namespace internal { 29namespace { 30 31// Bitmask of cache states in FileCacheEntry. 32enum TestFileCacheState { 33 TEST_CACHE_STATE_NONE = 0, 34 TEST_CACHE_STATE_PINNED = 1 << 0, 35 TEST_CACHE_STATE_PRESENT = 1 << 1, 36 TEST_CACHE_STATE_DIRTY = 1 << 2, 37}; 38 39// Copies results from Iterate(). 40void OnIterate(std::vector<std::string>* out_resource_ids, 41 std::vector<FileCacheEntry>* out_cache_entries, 42 const std::string& resource_id, 43 const FileCacheEntry& cache_entry) { 44 out_resource_ids->push_back(resource_id); 45 out_cache_entries->push_back(cache_entry); 46} 47 48// Called upon completion of Iterate(). 49void OnIterateCompleted(bool* out_is_called) { 50 *out_is_called = true; 51} 52 53} // namespace 54 55// Tests FileCache methods from UI thread. It internally uses a real blocking 56// pool and tests the interaction among threads. 57// TODO(hashimoto): remove this class. crbug.com/231221. 58class FileCacheTestOnUIThread : public testing::Test { 59 protected: 60 FileCacheTestOnUIThread() : expected_error_(FILE_ERROR_OK), 61 expected_cache_state_(0) { 62 } 63 64 virtual void SetUp() OVERRIDE { 65 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 66 ASSERT_TRUE(file_util::CreateDirectory( 67 temp_dir_.path().Append(util::kMetadataDirectory))); 68 ASSERT_TRUE(file_util::CreateDirectory( 69 temp_dir_.path().Append(util::kCacheFileDirectory))); 70 71 ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(), 72 &dummy_file_path_)); 73 fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter); 74 75 scoped_refptr<base::SequencedWorkerPool> pool = 76 content::BrowserThread::GetBlockingPool(); 77 blocking_task_runner_ = 78 pool->GetSequencedTaskRunner(pool->GetSequenceToken()); 79 80 metadata_storage_.reset(new ResourceMetadataStorage( 81 temp_dir_.path(), blocking_task_runner_.get())); 82 83 bool success = false; 84 base::PostTaskAndReplyWithResult( 85 blocking_task_runner_.get(), 86 FROM_HERE, 87 base::Bind(&ResourceMetadataStorage::Initialize, 88 base::Unretained(metadata_storage_.get())), 89 google_apis::test_util::CreateCopyResultCallback(&success)); 90 test_util::RunBlockingPoolTask(); 91 ASSERT_TRUE(success); 92 93 cache_.reset(new FileCache( 94 metadata_storage_.get(), 95 temp_dir_.path().Append(util::kCacheFileDirectory), 96 blocking_task_runner_.get(), 97 fake_free_disk_space_getter_.get())); 98 99 success = false; 100 base::PostTaskAndReplyWithResult( 101 blocking_task_runner_.get(), 102 FROM_HERE, 103 base::Bind(&FileCache::Initialize, base::Unretained(cache_.get())), 104 google_apis::test_util::CreateCopyResultCallback(&success)); 105 test_util::RunBlockingPoolTask(); 106 ASSERT_TRUE(success); 107 } 108 109 void TestGetFileFromCacheByResourceIdAndMd5( 110 const std::string& resource_id, 111 const std::string& md5, 112 FileError expected_error, 113 const std::string& expected_file_extension) { 114 FileError error = FILE_ERROR_OK; 115 base::FilePath cache_file_path; 116 cache_->GetFileOnUIThread(resource_id, md5, 117 google_apis::test_util::CreateCopyResultCallback( 118 &error, &cache_file_path)); 119 test_util::RunBlockingPoolTask(); 120 121 EXPECT_EQ(expected_error, error); 122 if (error == FILE_ERROR_OK) { 123 // Verify filename of |cache_file_path|. 124 EXPECT_EQ(util::EscapeCacheFileName(resource_id), 125 cache_file_path.BaseName().AsUTF8Unsafe()); 126 } else { 127 EXPECT_TRUE(cache_file_path.empty()); 128 } 129 } 130 131 void TestStoreToCache(const std::string& resource_id, 132 const std::string& md5, 133 const base::FilePath& source_path, 134 FileError expected_error, 135 int expected_cache_state) { 136 expected_error_ = expected_error; 137 expected_cache_state_ = expected_cache_state; 138 139 FileError error = FILE_ERROR_OK; 140 cache_->StoreOnUIThread( 141 resource_id, md5, source_path, 142 FileCache::FILE_OPERATION_COPY, 143 google_apis::test_util::CreateCopyResultCallback(&error)); 144 test_util::RunBlockingPoolTask(); 145 VerifyCacheFileState(error, resource_id, md5); 146 } 147 148 void TestRemoveFromCache(const std::string& resource_id, 149 FileError expected_error) { 150 expected_error_ = expected_error; 151 152 FileError error = FILE_ERROR_OK; 153 cache_->RemoveOnUIThread( 154 resource_id, 155 google_apis::test_util::CreateCopyResultCallback(&error)); 156 test_util::RunBlockingPoolTask(); 157 VerifyRemoveFromCache(error, resource_id, ""); 158 } 159 160 void VerifyRemoveFromCache(FileError error, 161 const std::string& resource_id, 162 const std::string& md5) { 163 EXPECT_EQ(expected_error_, error); 164 165 FileCacheEntry cache_entry; 166 if (!GetCacheEntryFromOriginThread(resource_id, md5, &cache_entry)) { 167 EXPECT_EQ(FILE_ERROR_OK, error); 168 169 const base::FilePath path = cache_->GetCacheFilePath(resource_id); 170 EXPECT_FALSE(base::PathExists(path)); 171 } 172 } 173 174 void TestPin(const std::string& resource_id, 175 FileError expected_error, 176 int expected_cache_state) { 177 expected_error_ = expected_error; 178 expected_cache_state_ = expected_cache_state; 179 180 FileError error = FILE_ERROR_OK; 181 cache_->PinOnUIThread( 182 resource_id, 183 google_apis::test_util::CreateCopyResultCallback(&error)); 184 test_util::RunBlockingPoolTask(); 185 VerifyCacheFileState(error, resource_id, std::string()); 186 } 187 188 void TestUnpin(const std::string& resource_id, 189 FileError expected_error, 190 int expected_cache_state) { 191 expected_error_ = expected_error; 192 expected_cache_state_ = expected_cache_state; 193 194 FileError error = FILE_ERROR_OK; 195 cache_->UnpinOnUIThread( 196 resource_id, 197 google_apis::test_util::CreateCopyResultCallback(&error)); 198 test_util::RunBlockingPoolTask(); 199 VerifyCacheFileState(error, resource_id, std::string()); 200 } 201 202 void TestMarkDirty(const std::string& resource_id, 203 const std::string& md5, 204 FileError expected_error, 205 int expected_cache_state) { 206 expected_error_ = expected_error; 207 expected_cache_state_ = expected_cache_state; 208 209 FileError error = FILE_ERROR_OK; 210 cache_->MarkDirtyOnUIThread( 211 resource_id, md5, 212 google_apis::test_util::CreateCopyResultCallback(&error)); 213 test_util::RunBlockingPoolTask(); 214 215 VerifyCacheFileState(error, resource_id, md5); 216 217 // Verify filename. 218 if (error == FILE_ERROR_OK) { 219 base::FilePath cache_file_path; 220 cache_->GetFileOnUIThread( 221 resource_id, md5, 222 google_apis::test_util::CreateCopyResultCallback( 223 &error, &cache_file_path)); 224 test_util::RunBlockingPoolTask(); 225 226 EXPECT_EQ(FILE_ERROR_OK, error); 227 EXPECT_EQ(util::EscapeCacheFileName(resource_id), 228 cache_file_path.BaseName().AsUTF8Unsafe()); 229 } 230 } 231 232 void TestClearDirty(const std::string& resource_id, 233 const std::string& md5, 234 FileError expected_error, 235 int expected_cache_state) { 236 expected_error_ = expected_error; 237 expected_cache_state_ = expected_cache_state; 238 239 FileError error = FILE_ERROR_OK; 240 PostTaskAndReplyWithResult( 241 blocking_task_runner_.get(), 242 FROM_HERE, 243 base::Bind(&FileCache::ClearDirty, 244 base::Unretained(cache_.get()), 245 resource_id, 246 md5), 247 google_apis::test_util::CreateCopyResultCallback(&error)); 248 test_util::RunBlockingPoolTask(); 249 VerifyCacheFileState(error, resource_id, md5); 250 } 251 252 void TestMarkAsMounted(const std::string& resource_id, 253 FileError expected_error, 254 int expected_cache_state) { 255 expected_error_ = expected_error; 256 expected_cache_state_ = expected_cache_state; 257 258 FileCacheEntry entry; 259 EXPECT_TRUE(GetCacheEntryFromOriginThread(resource_id, std::string(), 260 &entry)); 261 262 FileError error = FILE_ERROR_OK; 263 base::FilePath cache_file_path; 264 cache_->MarkAsMountedOnUIThread( 265 resource_id, 266 google_apis::test_util::CreateCopyResultCallback( 267 &error, &cache_file_path)); 268 test_util::RunBlockingPoolTask(); 269 270 EXPECT_TRUE(base::PathExists(cache_file_path)); 271 EXPECT_EQ(cache_file_path, cache_->GetCacheFilePath(resource_id)); 272 } 273 274 void TestMarkAsUnmounted(const std::string& resource_id, 275 const std::string& md5, 276 const base::FilePath& file_path, 277 FileError expected_error, 278 int expected_cache_state) { 279 expected_error_ = expected_error; 280 expected_cache_state_ = expected_cache_state; 281 282 FileError error = FILE_ERROR_OK; 283 cache_->MarkAsUnmountedOnUIThread( 284 file_path, 285 google_apis::test_util::CreateCopyResultCallback(&error)); 286 test_util::RunBlockingPoolTask(); 287 288 base::FilePath cache_file_path; 289 cache_->GetFileOnUIThread( 290 resource_id, md5, 291 google_apis::test_util::CreateCopyResultCallback( 292 &error, &cache_file_path)); 293 test_util::RunBlockingPoolTask(); 294 EXPECT_EQ(FILE_ERROR_OK, error); 295 296 EXPECT_TRUE(base::PathExists(cache_file_path)); 297 EXPECT_EQ(cache_file_path, cache_->GetCacheFilePath(resource_id)); 298 } 299 300 void VerifyCacheFileState(FileError error, 301 const std::string& resource_id, 302 const std::string& md5) { 303 EXPECT_EQ(expected_error_, error); 304 305 // Verify cache map. 306 FileCacheEntry cache_entry; 307 const bool cache_entry_found = 308 GetCacheEntryFromOriginThread(resource_id, md5, &cache_entry); 309 if ((expected_cache_state_ & TEST_CACHE_STATE_PRESENT) || 310 (expected_cache_state_ & TEST_CACHE_STATE_PINNED)) { 311 ASSERT_TRUE(cache_entry_found); 312 EXPECT_EQ((expected_cache_state_ & TEST_CACHE_STATE_PINNED) != 0, 313 cache_entry.is_pinned()); 314 EXPECT_EQ((expected_cache_state_ & TEST_CACHE_STATE_PRESENT) != 0, 315 cache_entry.is_present()); 316 EXPECT_EQ((expected_cache_state_ & TEST_CACHE_STATE_DIRTY) != 0, 317 cache_entry.is_dirty()); 318 } else { 319 EXPECT_FALSE(cache_entry_found); 320 } 321 322 // Verify actual cache file. 323 base::FilePath dest_path = cache_->GetCacheFilePath(resource_id); 324 EXPECT_EQ((expected_cache_state_ & TEST_CACHE_STATE_PRESENT) != 0, 325 base::PathExists(dest_path)); 326 } 327 328 // Helper function to call GetCacheEntry from origin thread. 329 bool GetCacheEntryFromOriginThread(const std::string& resource_id, 330 const std::string& md5, 331 FileCacheEntry* cache_entry) { 332 bool result = false; 333 cache_->GetCacheEntryOnUIThread( 334 resource_id, md5, 335 google_apis::test_util::CreateCopyResultCallback(&result, cache_entry)); 336 test_util::RunBlockingPoolTask(); 337 return result; 338 } 339 340 // Returns true if the cache entry exists for the given resource ID and MD5. 341 bool CacheEntryExists(const std::string& resource_id, 342 const std::string& md5) { 343 FileCacheEntry cache_entry; 344 return GetCacheEntryFromOriginThread(resource_id, md5, &cache_entry); 345 } 346 347 // Returns the number of the cache files with name <resource_id>, and Confirm 348 // that they have the <md5>. This should return 1 or 0. 349 size_t CountCacheFiles(const std::string& resource_id, 350 const std::string& md5) { 351 base::FilePath path = cache_->GetCacheFilePath(resource_id); 352 base::FileEnumerator enumerator(path.DirName(), 353 false, // recursive 354 base::FileEnumerator::FILES, 355 path.BaseName().value()); 356 size_t num_files_found = 0; 357 for (base::FilePath current = enumerator.Next(); !current.empty(); 358 current = enumerator.Next()) { 359 ++num_files_found; 360 EXPECT_EQ(util::EscapeCacheFileName(resource_id), 361 current.BaseName().AsUTF8Unsafe()); 362 } 363 return num_files_found; 364 } 365 366 content::TestBrowserThreadBundle thread_bundle_; 367 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_; 368 base::ScopedTempDir temp_dir_; 369 base::FilePath dummy_file_path_; 370 371 scoped_ptr<ResourceMetadataStorage, test_util::DestroyHelperForTests> 372 metadata_storage_; 373 scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_; 374 scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_; 375 376 FileError expected_error_; 377 int expected_cache_state_; 378 std::string expected_file_extension_; 379}; 380 381TEST_F(FileCacheTestOnUIThread, StoreToCacheSimple) { 382 std::string resource_id("pdf:1a2b"); 383 std::string md5("abcdef0123456789"); 384 385 // Store an existing file. 386 TestStoreToCache(resource_id, md5, dummy_file_path_, 387 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 388 389 // Store a non-existent file to the same |resource_id| and |md5|. 390 TestStoreToCache(resource_id, md5, 391 base::FilePath::FromUTF8Unsafe("non_existent_file"), 392 FILE_ERROR_FAILED, 393 TEST_CACHE_STATE_PRESENT); 394 395 // Store a different existing file to the same |resource_id| but different 396 // |md5|. 397 md5 = "new_md5"; 398 TestStoreToCache(resource_id, md5, dummy_file_path_, 399 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 400 401 // Verify that there's only one file with name <resource_id>, i.e. previously 402 // cached file with the different md5 should be deleted. 403 EXPECT_EQ(1U, CountCacheFiles(resource_id, md5)); 404} 405 406 407TEST_F(FileCacheTestOnUIThread, GetFromCacheSimple) { 408 std::string resource_id("pdf:1a2b"); 409 std::string md5("abcdef0123456789"); 410 // First store a file to cache. 411 TestStoreToCache(resource_id, md5, dummy_file_path_, 412 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 413 414 // Then try to get the existing file from cache. 415 TestGetFileFromCacheByResourceIdAndMd5( 416 resource_id, md5, FILE_ERROR_OK, md5); 417 418 // Get file from cache with same resource id as existing file but different 419 // md5. 420 TestGetFileFromCacheByResourceIdAndMd5( 421 resource_id, "9999", FILE_ERROR_NOT_FOUND, md5); 422 423 // Get file from cache with different resource id from existing file but same 424 // md5. 425 resource_id = "document:1a2b"; 426 TestGetFileFromCacheByResourceIdAndMd5( 427 resource_id, md5, FILE_ERROR_NOT_FOUND, md5); 428} 429 430TEST_F(FileCacheTestOnUIThread, RemoveFromCacheSimple) { 431 // Use alphanumeric characters for resource id. 432 std::string resource_id("pdf:1a2b"); 433 std::string md5("abcdef0123456789"); 434 // First store a file to cache. 435 TestStoreToCache(resource_id, md5, dummy_file_path_, 436 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 437 438 // Then try to remove existing file from cache. 439 TestRemoveFromCache(resource_id, FILE_ERROR_OK); 440 441 // Repeat using non-alphanumeric characters for resource id, including '.' 442 // which is an extension separator. 443 resource_id = "pdf:`~!@#$%^&*()-_=+[{|]}\\;',<.>/?"; 444 TestStoreToCache(resource_id, md5, dummy_file_path_, 445 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 446 447 TestRemoveFromCache(resource_id, FILE_ERROR_OK); 448} 449 450TEST_F(FileCacheTestOnUIThread, PinAndUnpin) { 451 std::string resource_id("pdf:1a2b"); 452 std::string md5("abcdef0123456789"); 453 454 // First store a file to cache. 455 TestStoreToCache(resource_id, md5, dummy_file_path_, 456 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 457 458 // Pin the existing file in cache. 459 TestPin(resource_id, FILE_ERROR_OK, 460 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); 461 462 // Unpin the existing file in cache. 463 TestUnpin(resource_id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 464 465 // Pin back the same existing file in cache. 466 TestPin(resource_id, FILE_ERROR_OK, 467 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); 468 469 // Pin a non-existent file in cache. 470 resource_id = "document:1a2b"; 471 472 TestPin(resource_id, FILE_ERROR_OK, TEST_CACHE_STATE_PINNED); 473 474 // Unpin the previously pinned non-existent file in cache. 475 TestUnpin(resource_id, FILE_ERROR_OK, TEST_CACHE_STATE_NONE); 476 477 // Unpin a file that doesn't exist in cache and is not pinned, i.e. cache 478 // has zero knowledge of the file. 479 resource_id = "not-in-cache:1a2b"; 480 481 TestUnpin(resource_id, FILE_ERROR_NOT_FOUND, TEST_CACHE_STATE_NONE); 482} 483 484TEST_F(FileCacheTestOnUIThread, StoreToCachePinned) { 485 std::string resource_id("pdf:1a2b"); 486 std::string md5("abcdef0123456789"); 487 488 // Pin a non-existent file. 489 TestPin(resource_id, FILE_ERROR_OK, TEST_CACHE_STATE_PINNED); 490 491 // Store an existing file to a previously pinned file. 492 TestStoreToCache(resource_id, md5, dummy_file_path_, FILE_ERROR_OK, 493 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); 494 495 // Store a non-existent file to a previously pinned and stored file. 496 TestStoreToCache(resource_id, md5, 497 base::FilePath::FromUTF8Unsafe("non_existent_file"), 498 FILE_ERROR_FAILED, 499 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); 500} 501 502TEST_F(FileCacheTestOnUIThread, GetFromCachePinned) { 503 std::string resource_id("pdf:1a2b"); 504 std::string md5("abcdef0123456789"); 505 506 // Pin a non-existent file. 507 TestPin(resource_id, FILE_ERROR_OK, TEST_CACHE_STATE_PINNED); 508 509 // Get the non-existent pinned file from cache. 510 TestGetFileFromCacheByResourceIdAndMd5( 511 resource_id, md5, FILE_ERROR_NOT_FOUND, md5); 512 513 // Store an existing file to the previously pinned non-existent file. 514 TestStoreToCache(resource_id, md5, dummy_file_path_, FILE_ERROR_OK, 515 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); 516 517 // Get the previously pinned and stored file from cache. 518 TestGetFileFromCacheByResourceIdAndMd5( 519 resource_id, md5, FILE_ERROR_OK, md5); 520} 521 522TEST_F(FileCacheTestOnUIThread, RemoveFromCachePinned) { 523 // Use alphanumeric characters for resource_id. 524 std::string resource_id("pdf:1a2b"); 525 std::string md5("abcdef0123456789"); 526 527 // Store a file to cache, and pin it. 528 TestStoreToCache(resource_id, md5, dummy_file_path_, 529 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 530 TestPin(resource_id, FILE_ERROR_OK, 531 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); 532 533 // Remove |resource_id| from cache. 534 TestRemoveFromCache(resource_id, FILE_ERROR_OK); 535 536 // Repeat using non-alphanumeric characters for resource id, including '.' 537 // which is an extension separator. 538 resource_id = "pdf:`~!@#$%^&*()-_=+[{|]}\\;',<.>/?"; 539 540 TestStoreToCache(resource_id, md5, dummy_file_path_, 541 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 542 TestPin(resource_id, FILE_ERROR_OK, 543 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); 544 545 TestRemoveFromCache(resource_id, FILE_ERROR_OK); 546} 547 548TEST_F(FileCacheTestOnUIThread, DirtyCacheSimple) { 549 std::string resource_id("pdf:1a2b"); 550 std::string md5("abcdef0123456789"); 551 552 // First store a file to cache. 553 TestStoreToCache(resource_id, md5, dummy_file_path_, 554 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 555 556 // Mark the file dirty. 557 TestMarkDirty(resource_id, md5, FILE_ERROR_OK, 558 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); 559 560 // Clear dirty state of the file. 561 TestClearDirty(resource_id, md5, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 562} 563 564TEST_F(FileCacheTestOnUIThread, DirtyCachePinned) { 565 std::string resource_id("pdf:1a2b"); 566 std::string md5("abcdef0123456789"); 567 568 // First store a file to cache and pin it. 569 TestStoreToCache(resource_id, md5, dummy_file_path_, 570 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 571 TestPin(resource_id, FILE_ERROR_OK, 572 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); 573 574 // Mark the file dirty. 575 TestMarkDirty(resource_id, md5, FILE_ERROR_OK, 576 TEST_CACHE_STATE_PRESENT | 577 TEST_CACHE_STATE_DIRTY | 578 TEST_CACHE_STATE_PINNED); 579 580 // Clear dirty state of the file. 581 TestClearDirty(resource_id, md5, FILE_ERROR_OK, 582 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); 583} 584 585TEST_F(FileCacheTestOnUIThread, PinAndUnpinDirtyCache) { 586 std::string resource_id("pdf:1a2b"); 587 std::string md5("abcdef0123456789"); 588 589 // First store a file to cache and mark it as dirty. 590 TestStoreToCache(resource_id, md5, dummy_file_path_, 591 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 592 TestMarkDirty(resource_id, md5, FILE_ERROR_OK, 593 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); 594 595 // Verifies dirty file exists. 596 base::FilePath dirty_path; 597 FileError error = FILE_ERROR_FAILED; 598 cache_->GetFileOnUIThread( 599 resource_id, md5, 600 google_apis::test_util::CreateCopyResultCallback(&error, &dirty_path)); 601 test_util::RunBlockingPoolTask(); 602 EXPECT_EQ(FILE_ERROR_OK, error); 603 EXPECT_TRUE(base::PathExists(dirty_path)); 604 605 // Pin the dirty file. 606 TestPin(resource_id, FILE_ERROR_OK, 607 TEST_CACHE_STATE_PRESENT | 608 TEST_CACHE_STATE_DIRTY | 609 TEST_CACHE_STATE_PINNED); 610 611 // Verify dirty file still exist at the same pathname. 612 EXPECT_TRUE(base::PathExists(dirty_path)); 613 614 // Unpin the dirty file. 615 TestUnpin(resource_id, FILE_ERROR_OK, 616 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); 617 618 // Verify dirty file still exist at the same pathname. 619 EXPECT_TRUE(base::PathExists(dirty_path)); 620} 621 622TEST_F(FileCacheTestOnUIThread, DirtyCacheRepetitive) { 623 std::string resource_id("pdf:1a2b"); 624 std::string md5("abcdef0123456789"); 625 626 // First store a file to cache. 627 TestStoreToCache(resource_id, md5, dummy_file_path_, 628 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 629 630 // Mark the file dirty. 631 TestMarkDirty(resource_id, md5, FILE_ERROR_OK, 632 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); 633 634 // Again, mark the file dirty. Nothing should change. 635 TestMarkDirty(resource_id, md5, FILE_ERROR_OK, 636 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); 637 638 // Clear dirty state of the file. 639 TestClearDirty(resource_id, md5, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 640 641 // Again, clear dirty state of the file, which is no longer dirty. 642 TestClearDirty(resource_id, md5, FILE_ERROR_INVALID_OPERATION, 643 TEST_CACHE_STATE_PRESENT); 644} 645 646TEST_F(FileCacheTestOnUIThread, DirtyCacheInvalid) { 647 std::string resource_id("pdf:1a2b"); 648 std::string md5("abcdef0123456789"); 649 650 // Mark a non-existent file dirty. 651 TestMarkDirty(resource_id, md5, FILE_ERROR_NOT_FOUND, TEST_CACHE_STATE_NONE); 652 653 // Clear dirty state of a non-existent file. 654 TestClearDirty(resource_id, md5, FILE_ERROR_NOT_FOUND, TEST_CACHE_STATE_NONE); 655 656 // Store a file to cache. 657 TestStoreToCache(resource_id, md5, dummy_file_path_, 658 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 659 660 // Clear dirty state of a non-dirty existing file. 661 TestClearDirty(resource_id, md5, FILE_ERROR_INVALID_OPERATION, 662 TEST_CACHE_STATE_PRESENT); 663 664 // Mark an existing file dirty, then store a new file to the same resource id 665 // but different md5, which should fail. 666 TestMarkDirty(resource_id, md5, FILE_ERROR_OK, 667 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); 668 md5 = "new_md5"; 669 TestStoreToCache(resource_id, md5, dummy_file_path_, 670 FILE_ERROR_IN_USE, 671 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY); 672} 673 674TEST_F(FileCacheTestOnUIThread, RemoveFromDirtyCache) { 675 std::string resource_id("pdf:1a2b"); 676 std::string md5("abcdef0123456789"); 677 678 // Store a file to cache, pin it, mark it dirty and commit it. 679 TestStoreToCache(resource_id, md5, dummy_file_path_, 680 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 681 TestPin(resource_id, FILE_ERROR_OK, 682 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); 683 TestMarkDirty(resource_id, md5, FILE_ERROR_OK, 684 TEST_CACHE_STATE_PRESENT | 685 TEST_CACHE_STATE_PINNED | 686 TEST_CACHE_STATE_DIRTY); 687 688 // Try to remove the file. Dirty caches can be removed at the level of 689 // FileCache::Remove. Upper layer cache clearance functions like 690 // FreeDiskSpaceIfNeededFor() and RemoveStaleCacheFiles() takes care of 691 // securing dirty files. 692 TestRemoveFromCache(resource_id, FILE_ERROR_OK); 693} 694 695TEST_F(FileCacheTestOnUIThread, MountUnmount) { 696 std::string resource_id("pdf:1a2b"); 697 std::string md5("abcdef0123456789"); 698 699 // First store a file to cache. 700 TestStoreToCache(resource_id, md5, dummy_file_path_, 701 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 702 703 // Mark the file mounted. 704 TestMarkAsMounted(resource_id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 705 EXPECT_TRUE(CacheEntryExists(resource_id, md5)); 706 707 // Try to remove the file. 708 TestRemoveFromCache(resource_id, FILE_ERROR_IN_USE); 709 710 // Clear mounted state of the file. 711 base::FilePath file_path; 712 FileError error = FILE_ERROR_FAILED; 713 cache_->GetFileOnUIThread( 714 resource_id, md5, 715 google_apis::test_util::CreateCopyResultCallback(&error, &file_path)); 716 test_util::RunBlockingPoolTask(); 717 EXPECT_EQ(FILE_ERROR_OK, error); 718 719 TestMarkAsUnmounted(resource_id, md5, file_path, FILE_ERROR_OK, 720 TEST_CACHE_STATE_PRESENT); 721 EXPECT_TRUE(CacheEntryExists(resource_id, md5)); 722 723 // Try to remove the file. 724 TestRemoveFromCache(resource_id, FILE_ERROR_OK); 725} 726 727TEST_F(FileCacheTestOnUIThread, Iterate) { 728 const std::vector<test_util::TestCacheResource> cache_resources( 729 test_util::GetDefaultTestCacheResources()); 730 ASSERT_TRUE(test_util::PrepareTestCacheResources(cache_.get(), 731 cache_resources)); 732 733 std::vector<std::string> resource_ids; 734 std::vector<FileCacheEntry> cache_entries; 735 bool completed = false; 736 cache_->IterateOnUIThread( 737 base::Bind(&OnIterate, &resource_ids, &cache_entries), 738 base::Bind(&OnIterateCompleted, &completed)); 739 test_util::RunBlockingPoolTask(); 740 741 ASSERT_TRUE(completed); 742 743 sort(resource_ids.begin(), resource_ids.end()); 744 ASSERT_EQ(6U, resource_ids.size()); 745 EXPECT_EQ("dirty:existing", resource_ids[0]); 746 EXPECT_EQ("dirty_and_pinned:existing", resource_ids[1]); 747 EXPECT_EQ("pinned:existing", resource_ids[2]); 748 EXPECT_EQ("pinned:non-existent", resource_ids[3]); 749 EXPECT_EQ("tmp:`~!@#$%^&*()-_=+[{|]}\\;',<.>/?", resource_ids[4]); 750 EXPECT_EQ("tmp:resource_id", resource_ids[5]); 751 752 ASSERT_EQ(6U, cache_entries.size()); 753} 754 755TEST_F(FileCacheTestOnUIThread, ClearAll) { 756 std::string resource_id("pdf:1a2b"); 757 std::string md5("abcdef0123456789"); 758 759 // Store an existing file. 760 TestStoreToCache(resource_id, md5, dummy_file_path_, 761 FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT); 762 763 // Verify that there's only one cached file. 764 EXPECT_EQ(1U, CountCacheFiles(resource_id, md5)); 765 766 // Clear cache. 767 bool success = false; 768 cache_->ClearAllOnUIThread( 769 google_apis::test_util::CreateCopyResultCallback(&success)); 770 test_util::RunBlockingPoolTask(); 771 EXPECT_TRUE(success); 772 773 // Verify that all the cache is removed. 774 expected_error_ = FILE_ERROR_OK; 775 VerifyRemoveFromCache(FILE_ERROR_OK, resource_id, md5); 776 EXPECT_EQ(0U, CountCacheFiles(resource_id, md5)); 777} 778 779TEST_F(FileCacheTestOnUIThread, StoreToCacheNoSpace) { 780 fake_free_disk_space_getter_->set_default_value(0); 781 782 std::string resource_id("pdf:1a2b"); 783 std::string md5("abcdef0123456789"); 784 785 // Try to store an existing file. 786 TestStoreToCache(resource_id, md5, dummy_file_path_, FILE_ERROR_NO_SPACE, 787 TEST_CACHE_STATE_NONE); 788 789 // Verify that there's no files added. 790 EXPECT_EQ(0U, CountCacheFiles(resource_id, md5)); 791} 792 793TEST_F(FileCacheTestOnUIThread, UpdatePinnedCache) { 794 std::string resource_id("pdf:1a2b"); 795 std::string md5("abcdef0123456789"); 796 std::string md5_modified("aaaaaa0000000000"); 797 798 // Store an existing file. 799 TestStoreToCache(resource_id, md5, dummy_file_path_, FILE_ERROR_OK, 800 TEST_CACHE_STATE_PRESENT); 801 802 // Pin the file. 803 TestPin(resource_id, FILE_ERROR_OK, 804 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); 805 806 // Store the file with a modified content and md5. It should stay pinned. 807 TestStoreToCache(resource_id, md5_modified, dummy_file_path_, FILE_ERROR_OK, 808 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED); 809} 810 811// Tests FileCache methods working with the blocking task runner. 812class FileCacheTest : public testing::Test { 813 protected: 814 virtual void SetUp() OVERRIDE { 815 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 816 ASSERT_TRUE(file_util::CreateDirectory( 817 temp_dir_.path().Append(util::kMetadataDirectory))); 818 ASSERT_TRUE(file_util::CreateDirectory( 819 temp_dir_.path().Append(util::kCacheFileDirectory))); 820 821 fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter); 822 823 metadata_storage_.reset(new ResourceMetadataStorage( 824 temp_dir_.path().Append(util::kMetadataDirectory), 825 base::MessageLoopProxy::current().get())); 826 ASSERT_TRUE(metadata_storage_->Initialize()); 827 828 cache_.reset(new FileCache( 829 metadata_storage_.get(), 830 temp_dir_.path().Append(util::kCacheFileDirectory), 831 base::MessageLoopProxy::current().get(), 832 fake_free_disk_space_getter_.get())); 833 ASSERT_TRUE(cache_->Initialize()); 834 } 835 836 static bool ImportOldDB(FileCache* cache, const base::FilePath& old_db_path) { 837 return cache->ImportOldDB(old_db_path); 838 } 839 840 static void RenameCacheFilesToNewFormat(FileCache* cache) { 841 cache->RenameCacheFilesToNewFormat(); 842 } 843 844 content::TestBrowserThreadBundle thread_bundle_; 845 base::ScopedTempDir temp_dir_; 846 847 scoped_ptr<ResourceMetadataStorage, test_util::DestroyHelperForTests> 848 metadata_storage_; 849 scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_; 850 scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_; 851}; 852 853TEST_F(FileCacheTest, ScanCacheFile) { 854 // Set up files in the cache directory. 855 const base::FilePath file_directory = 856 temp_dir_.path().Append(util::kCacheFileDirectory); 857 ASSERT_TRUE(google_apis::test_util::WriteStringToFile( 858 file_directory.AppendASCII("id_foo"), "foo")); 859 860 // Remove the existing DB. 861 const base::FilePath metadata_directory = 862 temp_dir_.path().Append(util::kMetadataDirectory); 863 ASSERT_TRUE(base::DeleteFile(metadata_directory, true /* recursive */)); 864 865 // Put an empty file with the same name as old DB. 866 // This file cannot be opened by ImportOldDB() and will be dismissed. 867 ASSERT_TRUE(file_util::CreateDirectory(metadata_directory)); 868 ASSERT_TRUE(google_apis::test_util::WriteStringToFile( 869 metadata_directory.Append(FileCache::kOldCacheMetadataDBName), "")); 870 871 // Create a new cache and initialize it. 872 metadata_storage_.reset(new ResourceMetadataStorage( 873 metadata_directory, base::MessageLoopProxy::current().get())); 874 ASSERT_TRUE(metadata_storage_->Initialize()); 875 876 cache_.reset(new FileCache(metadata_storage_.get(), 877 temp_dir_.path().Append(util::kCacheFileDirectory), 878 base::MessageLoopProxy::current().get(), 879 fake_free_disk_space_getter_.get())); 880 ASSERT_TRUE(cache_->Initialize()); 881 882 // Check contents of the cache. 883 FileCacheEntry cache_entry; 884 EXPECT_TRUE(cache_->GetCacheEntry("id_foo", std::string(), &cache_entry)); 885 EXPECT_TRUE(cache_entry.is_present()); 886 EXPECT_EQ(base::MD5String("foo"), cache_entry.md5()); 887} 888 889TEST_F(FileCacheTest, FreeDiskSpaceIfNeededFor) { 890 base::FilePath src_file; 891 ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &src_file)); 892 893 // Store a file as a 'temporary' file and remember the path. 894 const std::string resource_id_tmp = "id_tmp", md5_tmp = "md5_tmp"; 895 ASSERT_EQ(FILE_ERROR_OK, 896 cache_->Store(resource_id_tmp, md5_tmp, src_file, 897 FileCache::FILE_OPERATION_COPY)); 898 base::FilePath tmp_path; 899 ASSERT_EQ(FILE_ERROR_OK, 900 cache_->GetFile(resource_id_tmp, md5_tmp, &tmp_path)); 901 902 // Store a file as a pinned file and remember the path. 903 const std::string resource_id_pinned = "id_pinned", md5_pinned = "md5_pinned"; 904 ASSERT_EQ(FILE_ERROR_OK, 905 cache_->Store(resource_id_pinned, md5_pinned, src_file, 906 FileCache::FILE_OPERATION_COPY)); 907 ASSERT_EQ(FILE_ERROR_OK, cache_->Pin(resource_id_pinned)); 908 base::FilePath pinned_path; 909 ASSERT_EQ(FILE_ERROR_OK, 910 cache_->GetFile(resource_id_pinned, md5_pinned, &pinned_path)); 911 912 // Call FreeDiskSpaceIfNeededFor(). 913 fake_free_disk_space_getter_->set_default_value(test_util::kLotsOfSpace); 914 fake_free_disk_space_getter_->PushFakeValue(0); 915 const int64 kNeededBytes = 1; 916 EXPECT_TRUE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes)); 917 918 // Only 'temporary' file gets removed. 919 FileCacheEntry entry; 920 EXPECT_FALSE(cache_->GetCacheEntry(resource_id_tmp, md5_tmp, &entry)); 921 EXPECT_FALSE(base::PathExists(tmp_path)); 922 923 EXPECT_TRUE(cache_->GetCacheEntry(resource_id_pinned, md5_pinned, &entry)); 924 EXPECT_TRUE(base::PathExists(pinned_path)); 925 926 // Returns false when disk space cannot be freed. 927 fake_free_disk_space_getter_->set_default_value(0); 928 EXPECT_FALSE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes)); 929} 930 931TEST_F(FileCacheTest, ImportOldDB) { 932 const base::FilePath old_db_path = temp_dir_.path().AppendASCII("old_db.db"); 933 934 const std::string key1 = "key1"; 935 const std::string md5_1 = "md5_1"; 936 const std::string key2 = "key2"; 937 const std::string md5_2 = "md5_2"; 938 939 // Set up data to be imported. 940 { 941 FileCacheMetadata old_metadata(NULL); 942 ASSERT_TRUE(old_metadata.Initialize(old_db_path)); 943 944 FileCacheEntry entry; 945 entry.set_md5(md5_1); 946 old_metadata.AddOrUpdateCacheEntry(key1, entry); 947 948 entry.set_md5(md5_2); 949 old_metadata.AddOrUpdateCacheEntry(key2, entry); 950 } 951 EXPECT_TRUE(base::PathExists(old_db_path)); 952 953 // Do import. 954 EXPECT_TRUE(ImportOldDB(cache_.get(), old_db_path)); 955 956 // Old DB should be removed. 957 EXPECT_FALSE(base::PathExists(old_db_path)); 958 959 // Data is imported correctly. 960 FileCacheEntry entry; 961 EXPECT_TRUE(cache_->GetCacheEntry(key1, std::string(), &entry)); 962 EXPECT_EQ(md5_1, entry.md5()); 963 EXPECT_TRUE(cache_->GetCacheEntry(key2, std::string(), &entry)); 964 EXPECT_EQ(md5_2, entry.md5()); 965} 966 967TEST_F(FileCacheTest, RenameCacheFilesToNewFormat) { 968 const base::FilePath file_directory = 969 temp_dir_.path().Append(util::kCacheFileDirectory); 970 971 // File with an old style "<resource ID>.<MD5>" name. 972 ASSERT_TRUE(google_apis::test_util::WriteStringToFile( 973 file_directory.AppendASCII("id_koo.md5"), "koo")); 974 975 // File with multiple extensions should be removed. 976 ASSERT_TRUE(google_apis::test_util::WriteStringToFile( 977 file_directory.AppendASCII("id_kyu.md5.mounted"), "kyu (mounted)")); 978 ASSERT_TRUE(google_apis::test_util::WriteStringToFile( 979 file_directory.AppendASCII("id_kyu.md5"), "kyu")); 980 981 // Rename and verify the result. 982 RenameCacheFilesToNewFormat(cache_.get()); 983 std::string contents; 984 EXPECT_TRUE(file_util::ReadFileToString(file_directory.AppendASCII("id_koo"), 985 &contents)); 986 EXPECT_EQ("koo", contents); 987 contents.clear(); 988 EXPECT_TRUE(file_util::ReadFileToString(file_directory.AppendASCII("id_kyu"), 989 &contents)); 990 EXPECT_EQ("kyu", contents); 991 992 // Rename again. 993 RenameCacheFilesToNewFormat(cache_.get()); 994 995 // Files with new style names are not affected. 996 contents.clear(); 997 EXPECT_TRUE(file_util::ReadFileToString(file_directory.AppendASCII("id_koo"), 998 &contents)); 999 EXPECT_EQ("koo", contents); 1000 contents.clear(); 1001 EXPECT_TRUE(file_util::ReadFileToString(file_directory.AppendASCII("id_kyu"), 1002 &contents)); 1003 EXPECT_EQ("kyu", contents); 1004} 1005 1006} // namespace internal 1007} // namespace drive 1008