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