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